From 0c2c451830d7f8b8ecacd8b2500e44f1cf5d7741 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 26 Jan 2021 21:56:31 +0200 Subject: [PATCH 01/16] [Security Solution][Case] Add button to go to case view after adding an alert to a case (#89214) --- .../add_to_case_action.test.tsx | 64 ++++++++++++++++--- .../timeline_actions/add_to_case_action.tsx | 23 ++++++- .../{helpers.test.ts => helpers.test.tsx} | 22 ++----- .../{helpers.ts => helpers.tsx} | 22 ++++--- .../timeline_actions/toaster_content.test.tsx | 45 +++++++++++++ .../timeline_actions/toaster_content.tsx | 46 +++++++++++++ .../timeline_actions/translations.ts | 7 ++ 7 files changed, 195 insertions(+), 34 deletions(-) rename x-pack/plugins/security_solution/public/cases/components/timeline_actions/{helpers.test.ts => helpers.test.tsx} (55%) rename x-pack/plugins/security_solution/public/cases/components/timeline_actions/{helpers.ts => helpers.tsx} (59%) create mode 100644 x-pack/plugins/security_solution/public/cases/components/timeline_actions/toaster_content.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/timeline_actions/toaster_content.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx index 0c156e247a5e0..71d7387d8d392 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx @@ -6,18 +6,24 @@ /* eslint-disable react/display-name */ import React, { ReactNode } from 'react'; - import { mount } from 'enzyme'; +import { EuiGlobalToastList } from '@elastic/eui'; + +import { useKibana } from '../../../common/lib/kibana'; +import { useStateToaster } from '../../../common/components/toasters'; import { TestProviders } from '../../../common/mock'; import { usePostComment } from '../../containers/use_post_comment'; +import { Case } from '../../containers/types'; import { AddToCaseAction } from './add_to_case_action'; jest.mock('../../containers/use_post_comment'); -jest.mock('../../../common/lib/kibana', () => { - const originalModule = jest.requireActual('../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); + +jest.mock('../../../common/components/toasters', () => { + const actual = jest.requireActual('../../../common/components/toasters'); return { - ...originalModule, - useGetUserSavedObjectPermissions: jest.fn(), + ...actual, + useStateToaster: jest.fn(), }; }); @@ -44,14 +50,16 @@ jest.mock('../create/form_context', () => { onSuccess, }: { children: ReactNode; - onSuccess: ({ id }: { id: string }) => void; + onSuccess: (theCase: Partial) => void; }) => { return ( <> @@ -95,9 +103,16 @@ describe('AddToCaseAction', () => { disabled: false, }; + const mockDispatchToaster = jest.fn(); + const mockNavigateToApp = jest.fn(); + beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); usePostCommentMock.mockImplementation(() => defaultPostComment); + (useStateToaster as jest.Mock).mockReturnValue([jest.fn(), mockDispatchToaster]); + (useKibana as jest.Mock).mockReturnValue({ + services: { application: { navigateToApp: mockNavigateToApp } }, + }); }); it('it renders', async () => { @@ -187,4 +202,37 @@ describe('AddToCaseAction', () => { type: 'alert', }); }); + + it('navigates to case view', async () => { + usePostCommentMock.mockImplementation(() => { + return { + ...defaultPostComment, + postComment: jest.fn().mockImplementation((caseId, data, updateCase) => updateCase()), + }; + }); + + const wrapper = mount( + + + + ); + + wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="form-context-on-success"]`).first().simulate('click'); + + expect(mockDispatchToaster).toHaveBeenCalled(); + const toast = mockDispatchToaster.mock.calls[0][0].toast; + + const toastWrapper = mount( + {}} /> + ); + + toastWrapper + .find('[data-test-subj="toaster-content-case-view-link"]') + .first() + .simulate('click'); + + expect(mockNavigateToApp).toHaveBeenCalledWith('securitySolution:case', { path: '/new-case' }); + }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx index 3ebc0654fc019..eed4f2092fd58 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx @@ -20,6 +20,10 @@ import { ActionIconItem } from '../../../timelines/components/timeline/body/acti import { usePostComment } from '../../containers/use_post_comment'; import { Case } from '../../containers/types'; import { useStateToaster } from '../../../common/components/toasters'; +import { APP_ID } from '../../../../common/constants'; +import { useKibana } from '../../../common/lib/kibana'; +import { getCaseDetailsUrl } from '../../../common/components/link_to'; +import { SecurityPageName } from '../../../app/types'; import { useCreateCaseModal } from '../use_create_case_modal'; import { useAllCasesModal } from '../use_all_cases_modal'; import { createUpdateSuccessToaster } from './helpers'; @@ -39,12 +43,23 @@ const AddToCaseActionComponent: React.FC = ({ const eventId = ecsRowData._id; const eventIndex = ecsRowData._index; + const { navigateToApp } = useKibana().services.application; const [, dispatchToaster] = useStateToaster(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const openPopover = useCallback(() => setIsPopoverOpen(true), []); const closePopover = useCallback(() => setIsPopoverOpen(false), []); const { postComment } = usePostComment(); + + const onViewCaseClick = useCallback( + (id) => { + navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + path: getCaseDetailsUrl({ id }), + }); + }, + [navigateToApp] + ); + const attachAlertToCase = useCallback( (theCase: Case) => { postComment( @@ -54,10 +69,14 @@ const AddToCaseActionComponent: React.FC = ({ alertId: eventId, index: eventIndex ?? '', }, - () => dispatchToaster({ type: 'addToaster', toast: createUpdateSuccessToaster(theCase) }) + () => + dispatchToaster({ + type: 'addToaster', + toast: createUpdateSuccessToaster(theCase, onViewCaseClick), + }) ); }, - [postComment, eventId, eventIndex, dispatchToaster] + [postComment, eventId, eventIndex, dispatchToaster, onViewCaseClick] ); const { modal: createCaseModal, openModal: openCreateCaseModal } = useCreateCaseModal({ diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.ts b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx similarity index 55% rename from x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.ts rename to x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx index 58c9c4baf82eb..b05dc4134cb10 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx @@ -8,6 +8,7 @@ import { createUpdateSuccessToaster } from './helpers'; import { Case } from '../../containers/types'; const theCase = { + id: 'case-id', title: 'My case', settings: { syncAlerts: true, @@ -15,24 +16,13 @@ const theCase = { } as Case; describe('helpers', () => { + const onViewCaseClick = jest.fn(); + describe('createUpdateSuccessToaster', () => { it('creates the correct toast when the sync alerts is on', () => { - // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster(theCase); - expect(toast).toEqual({ - color: 'success', - iconType: 'check', - text: 'Alerts in this case have their status synched with the case status', - title: 'An alert has been added to "My case"', - }); - }); - - it('creates the correct toast when the sync alerts is off', () => { - // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster({ - ...theCase, - settings: { syncAlerts: false }, - }); + // We remove the id as is randomly generated and the text as it is a React component + // which is being test on toaster_content.test.tsx + const { id, text, ...toast } = createUpdateSuccessToaster(theCase, onViewCaseClick); expect(toast).toEqual({ color: 'success', iconType: 'check', diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx similarity index 59% rename from x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.ts rename to x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx index abafa55c28903..b1bae8df0a0b1 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.ts +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx @@ -4,22 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import uuid from 'uuid'; import { AppToast } from '../../../common/components/toasters'; import { Case } from '../../containers/types'; +import { ToasterContent } from './toaster_content'; import * as i18n from './translations'; -export const createUpdateSuccessToaster = (theCase: Case): AppToast => { - const toast: AppToast = { +export const createUpdateSuccessToaster = ( + theCase: Case, + onViewCaseClick: (id: string) => void +): AppToast => { + return { id: uuid.v4(), color: 'success', iconType: 'check', title: i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title), + text: ( + + ), }; - - if (theCase.settings.syncAlerts) { - return { ...toast, text: i18n.CASE_CREATED_SUCCESS_TOAST_TEXT }; - } - - return toast; }; diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/toaster_content.test.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/toaster_content.test.tsx new file mode 100644 index 0000000000000..b04ebb8903aea --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/toaster_content.test.tsx @@ -0,0 +1,45 @@ +/* + * 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 { mount } from 'enzyme'; + +import { ToasterContent } from './toaster_content'; + +describe('ToasterContent', () => { + const onViewCaseClick = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders with syncAlerts=true', () => { + const wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="toaster-content-case-view-link"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="toaster-content-sync-text"]').exists()).toBeTruthy(); + }); + + it('renders with syncAlerts=false', () => { + const wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="toaster-content-case-view-link"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="toaster-content-sync-text"]').exists()).toBeFalsy(); + }); + + it('calls onViewCaseClick', () => { + const wrapper = mount( + + ); + + wrapper.find('[data-test-subj="toaster-content-case-view-link"]').first().simulate('click'); + expect(onViewCaseClick).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/toaster_content.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/toaster_content.tsx new file mode 100644 index 0000000000000..871db464d8576 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/toaster_content.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo, useCallback } from 'react'; +import { EuiButtonEmpty, EuiText } from '@elastic/eui'; +import styled from 'styled-components'; + +import * as i18n from './translations'; + +const EuiTextStyled = styled(EuiText)` + ${({ theme }) => ` + margin-bottom: ${theme.eui?.paddingSizes?.s ?? 8}px; + `} +`; + +interface Props { + caseId: string; + syncAlerts: boolean; + onViewCaseClick: (id: string) => void; +} + +const ToasterContentComponent: React.FC = ({ caseId, syncAlerts, onViewCaseClick }) => { + const onClick = useCallback(() => onViewCaseClick(caseId), [caseId, onViewCaseClick]); + return ( + <> + {syncAlerts && ( + + {i18n.CASE_CREATED_SUCCESS_TOAST_TEXT} + + )} + + {i18n.VIEW_CASE} + + + ); +}; + +export const ToasterContent = memo(ToasterContentComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/translations.ts b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/translations.ts index 479323ed1301c..dd3d6f0b50f19 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/translations.ts @@ -53,3 +53,10 @@ export const CASE_CREATED_SUCCESS_TOAST_TEXT = i18n.translate( defaultMessage: 'Alerts in this case have their status synched with the case status', } ); + +export const VIEW_CASE = i18n.translate( + 'xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToastViewCaseLink', + { + defaultMessage: 'View Case', + } +); From 87992d01da5787c0dd21f1186e8e8926cac97f07 Mon Sep 17 00:00:00 2001 From: Rashmi Kulkarni Date: Tue, 26 Jan 2021 12:49:44 -0800 Subject: [PATCH 02/16] Watcher -functional xpack test using test_user with specific permissions. (#89068) * fixes https://github.com/elastic/kibana/issues/74449 * watcher test with specific permissions * adding the false parameter Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/test/functional/apps/watcher/watcher_test.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/watcher/watcher_test.js b/x-pack/test/functional/apps/watcher/watcher_test.js index 1dd3fb6bbcc3d..2525f4d8f9621 100644 --- a/x-pack/test/functional/apps/watcher/watcher_test.js +++ b/x-pack/test/functional/apps/watcher/watcher_test.js @@ -15,16 +15,15 @@ export default function ({ getService, getPageObjects }) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); const log = getService('log'); + const security = getService('security'); const esSupertest = getService('esSupertest'); const PageObjects = getPageObjects(['security', 'common', 'header', 'settings', 'watcher']); - // Still flaky test :c - // https://github.com/elastic/kibana/pull/56361 - // https://github.com/elastic/kibana/pull/56304 - describe.skip('watcher_test', function () { + describe('watcher_test', function () { before('initialize tests', async () => { // There may be system watches if monitoring was previously enabled // These cannot be deleted via the UI, so we need to delete via the API + await security.testUser.setRoles(['kibana_admin', 'watcher_admin'], false); const watches = await esSupertest.get('/.watches/_search'); if (watches.status === 200) { @@ -56,6 +55,10 @@ export default function ({ getService, getPageObjects }) { }); }); + after(async () => { + await security.testUser.restoreDefaults(); + }); + it('create and save a new watch', async () => { await PageObjects.watcher.createWatch(watchID, watchName); const watch = await PageObjects.watcher.getWatch(watchID); From 0b6184769622e1e13c4890c3f7a9711b196e403c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 26 Jan 2021 14:32:09 -0700 Subject: [PATCH 03/16] [Maps] rename file_upload to maps_file_upload (#89225) * migrate file_upload plugin to maps_file_upload * update plugins list * results of running node scripts/build_plugin_list_docs * fix build problems * remove fileUpload from limits.yml Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/developer/plugin-list.asciidoc | 8 ++++---- packages/kbn-optimizer/limits.yml | 2 +- x-pack/.i18nrc.json | 2 +- x-pack/plugins/file_upload/README.md | 3 --- x-pack/plugins/maps/kibana.json | 2 +- .../public/classes/layers/file_upload_wizard/wizard.tsx | 2 +- x-pack/plugins/maps/public/kibana_services.ts | 2 +- x-pack/plugins/maps/public/plugin.ts | 4 ++-- x-pack/plugins/maps_file_upload/README.md | 3 +++ .../common/constants/file_import.ts | 0 .../{file_upload => maps_file_upload}/jest.config.js | 2 +- .../plugins/{file_upload => maps_file_upload}/kibana.json | 2 +- .../plugins/{file_upload => maps_file_upload}/mappings.ts | 0 .../public/components/index_settings.js | 0 .../public/components/json_import_progress.js | 0 .../public/components/json_index_file_picker.js | 0 .../public/components/json_upload_and_parse.js | 0 .../public/get_file_upload_component.ts | 0 .../{file_upload => maps_file_upload}/public/index.ts | 0 .../public/kibana_services.js | 0 .../{file_upload => maps_file_upload}/public/plugin.ts | 0 .../public/util/file_parser.js | 0 .../public/util/file_parser.test.js | 0 .../public/util/geo_json_clean_and_validate.js | 0 .../public/util/geo_json_clean_and_validate.test.js | 0 .../public/util/geo_processing.js | 0 .../public/util/geo_processing.test.js | 0 .../public/util/http_service.js | 0 .../public/util/indexing_service.js | 0 .../public/util/indexing_service.test.js | 0 .../public/util/size_limited_chunking.js | 0 .../public/util/size_limited_chunking.test.js | 0 .../server/client/errors.js | 0 .../{file_upload => maps_file_upload}/server/index.js | 0 .../server/kibana_server_services.js | 0 .../server/models/import_data/import_data.js | 0 .../server/models/import_data/index.js | 0 .../{file_upload => maps_file_upload}/server/plugin.js | 0 .../server/routes/file_upload.js | 0 .../server/routes/file_upload.test.js | 0 .../server/telemetry/file_upload_usage_collector.ts | 0 .../server/telemetry/index.ts | 0 .../server/telemetry/mappings.ts | 0 .../server/telemetry/telemetry.test.ts | 0 .../server/telemetry/telemetry.ts | 0 45 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 x-pack/plugins/file_upload/README.md create mode 100644 x-pack/plugins/maps_file_upload/README.md rename x-pack/plugins/{file_upload => maps_file_upload}/common/constants/file_import.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/jest.config.js (84%) rename x-pack/plugins/{file_upload => maps_file_upload}/kibana.json (83%) rename x-pack/plugins/{file_upload => maps_file_upload}/mappings.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/components/index_settings.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/components/json_import_progress.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/components/json_index_file_picker.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/components/json_upload_and_parse.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/get_file_upload_component.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/index.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/kibana_services.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/plugin.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/file_parser.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/file_parser.test.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/geo_json_clean_and_validate.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/geo_json_clean_and_validate.test.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/geo_processing.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/geo_processing.test.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/http_service.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/indexing_service.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/indexing_service.test.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/size_limited_chunking.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/public/util/size_limited_chunking.test.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/client/errors.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/index.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/kibana_server_services.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/models/import_data/import_data.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/models/import_data/index.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/plugin.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/routes/file_upload.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/routes/file_upload.test.js (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/telemetry/file_upload_usage_collector.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/telemetry/index.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/telemetry/mappings.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/telemetry/telemetry.test.ts (100%) rename x-pack/plugins/{file_upload => maps_file_upload}/server/telemetry/telemetry.ts (100%) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index ef3492a545b6a..7ce4896a8bce4 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -380,10 +380,6 @@ and actions. |The features plugin enhance Kibana with a per-feature privilege system. -|{kib-repo}blob/{branch}/x-pack/plugins/file_upload/README.md[fileUpload] -|Backend and core front-end react-components for GeoJson file upload. Only supports the Maps plugin. - - |{kib-repo}blob/{branch}/x-pack/plugins/fleet/README.md[fleet] |Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) @@ -453,6 +449,10 @@ using the CURL scripts in the scripts folder. |Visualize geo data from Elasticsearch or 3rd party geo-services. +|{kib-repo}blob/{branch}/x-pack/plugins/maps_file_upload/README.md[mapsFileUpload] +|Deprecated - plugin targeted for removal and will get merged into file_upload plugin + + |{kib-repo}blob/{branch}/x-pack/plugins/maps_legacy_licensing/README.md[mapsLegacyLicensing] |This plugin provides access to the detailed tile map services from Elastic. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 44cc4fdabb25e..ef672d6cbeb2e 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -25,7 +25,6 @@ pageLoadAssetSize: esUiShared: 326654 expressions: 224136 features: 31211 - fileUpload: 24717 globalSearch: 43548 globalSearchBar: 62888 globalSearchProviders: 25554 @@ -106,3 +105,4 @@ pageLoadAssetSize: stackAlerts: 29684 presentationUtil: 28545 spacesOss: 18817 + mapsFileUpload: 23775 diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 6937862d20536..bfac437f3500a 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -20,7 +20,7 @@ "xpack.endpoint": "plugins/endpoint", "xpack.enterpriseSearch": "plugins/enterprise_search", "xpack.features": "plugins/features", - "xpack.fileUpload": "plugins/file_upload", + "xpack.fileUpload": "plugins/maps_file_upload", "xpack.globalSearch": ["plugins/global_search"], "xpack.globalSearchBar": ["plugins/global_search_bar"], "xpack.graph": ["plugins/graph"], diff --git a/x-pack/plugins/file_upload/README.md b/x-pack/plugins/file_upload/README.md deleted file mode 100644 index 0d4b4da61ccf6..0000000000000 --- a/x-pack/plugins/file_upload/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# File upload - -Backend and core front-end react-components for GeoJson file upload. Only supports the Maps plugin. \ No newline at end of file diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index 42adf6f2a950b..5f6f5224e32a3 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -8,7 +8,7 @@ "features", "inspector", "data", - "fileUpload", + "mapsFileUpload", "uiActions", "navigation", "visualizations", diff --git a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx index 68fd25ce9e7ae..54e58c876a839 100644 --- a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx +++ b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx @@ -18,7 +18,7 @@ import { GeoJsonFileSource } from '../../sources/geojson_file_source'; import { VectorLayer } from '../../layers/vector_layer/vector_layer'; import { createDefaultLayerDescriptor } from '../../sources/es_search_source'; import { RenderWizardArguments } from '../../layers/layer_wizard_registry'; -import { FileUploadComponentProps } from '../../../../../file_upload/public'; +import { FileUploadComponentProps } from '../../../../../maps_file_upload/public'; export const INDEX_SETUP_STEP_ID = 'INDEX_SETUP_STEP_ID'; export const INDEXING_STEP_ID = 'INDEXING_STEP_ID'; diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 02b875257a5ac..99c9311a2a454 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -25,7 +25,7 @@ export const getIndexPatternService = () => pluginsStart.data.indexPatterns; export const getAutocompleteService = () => pluginsStart.data.autocomplete; export const getInspector = () => pluginsStart.inspector; export const getFileUploadComponent = async () => { - return await pluginsStart.fileUpload.getFileUploadComponent(); + return await pluginsStart.mapsFileUpload.getFileUploadComponent(); }; export const getUiSettings = () => coreStart.uiSettings; export const getIsDarkMode = () => getUiSettings().get('theme:darkMode', false); diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 690002a771601..dd256126fae62 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -54,7 +54,7 @@ import { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public'; -import { StartContract as FileUploadStartContract } from '../../file_upload/public'; +import { StartContract as FileUploadStartContract } from '../../maps_file_upload/public'; import { SavedObjectsStart } from '../../../../src/plugins/saved_objects/public'; import { getIsEnterprisePlus, @@ -77,7 +77,7 @@ export interface MapsPluginSetupDependencies { export interface MapsPluginStartDependencies { data: DataPublicPluginStart; embeddable: EmbeddableStart; - fileUpload: FileUploadStartContract; + mapsFileUpload: FileUploadStartContract; inspector: InspectorStartContract; licensing: LicensingPluginStart; navigation: NavigationPublicPluginStart; diff --git a/x-pack/plugins/maps_file_upload/README.md b/x-pack/plugins/maps_file_upload/README.md new file mode 100644 index 0000000000000..1e3343664afb8 --- /dev/null +++ b/x-pack/plugins/maps_file_upload/README.md @@ -0,0 +1,3 @@ +# Maps File upload + +Deprecated - plugin targeted for removal and will get merged into file_upload plugin diff --git a/x-pack/plugins/file_upload/common/constants/file_import.ts b/x-pack/plugins/maps_file_upload/common/constants/file_import.ts similarity index 100% rename from x-pack/plugins/file_upload/common/constants/file_import.ts rename to x-pack/plugins/maps_file_upload/common/constants/file_import.ts diff --git a/x-pack/plugins/file_upload/jest.config.js b/x-pack/plugins/maps_file_upload/jest.config.js similarity index 84% rename from x-pack/plugins/file_upload/jest.config.js rename to x-pack/plugins/maps_file_upload/jest.config.js index 6a042a4cc5c1e..2893da079141c 100644 --- a/x-pack/plugins/file_upload/jest.config.js +++ b/x-pack/plugins/maps_file_upload/jest.config.js @@ -7,5 +7,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', - roots: ['/x-pack/plugins/file_upload'], + roots: ['/x-pack/plugins/maps_file_upload'], }; diff --git a/x-pack/plugins/file_upload/kibana.json b/x-pack/plugins/maps_file_upload/kibana.json similarity index 83% rename from x-pack/plugins/file_upload/kibana.json rename to x-pack/plugins/maps_file_upload/kibana.json index 7676a01d0b0f9..f544c56cba517 100644 --- a/x-pack/plugins/file_upload/kibana.json +++ b/x-pack/plugins/maps_file_upload/kibana.json @@ -1,5 +1,5 @@ { - "id": "fileUpload", + "id": "mapsFileUpload", "version": "8.0.0", "kibanaVersion": "kibana", "server": true, diff --git a/x-pack/plugins/file_upload/mappings.ts b/x-pack/plugins/maps_file_upload/mappings.ts similarity index 100% rename from x-pack/plugins/file_upload/mappings.ts rename to x-pack/plugins/maps_file_upload/mappings.ts diff --git a/x-pack/plugins/file_upload/public/components/index_settings.js b/x-pack/plugins/maps_file_upload/public/components/index_settings.js similarity index 100% rename from x-pack/plugins/file_upload/public/components/index_settings.js rename to x-pack/plugins/maps_file_upload/public/components/index_settings.js diff --git a/x-pack/plugins/file_upload/public/components/json_import_progress.js b/x-pack/plugins/maps_file_upload/public/components/json_import_progress.js similarity index 100% rename from x-pack/plugins/file_upload/public/components/json_import_progress.js rename to x-pack/plugins/maps_file_upload/public/components/json_import_progress.js diff --git a/x-pack/plugins/file_upload/public/components/json_index_file_picker.js b/x-pack/plugins/maps_file_upload/public/components/json_index_file_picker.js similarity index 100% rename from x-pack/plugins/file_upload/public/components/json_index_file_picker.js rename to x-pack/plugins/maps_file_upload/public/components/json_index_file_picker.js diff --git a/x-pack/plugins/file_upload/public/components/json_upload_and_parse.js b/x-pack/plugins/maps_file_upload/public/components/json_upload_and_parse.js similarity index 100% rename from x-pack/plugins/file_upload/public/components/json_upload_and_parse.js rename to x-pack/plugins/maps_file_upload/public/components/json_upload_and_parse.js diff --git a/x-pack/plugins/file_upload/public/get_file_upload_component.ts b/x-pack/plugins/maps_file_upload/public/get_file_upload_component.ts similarity index 100% rename from x-pack/plugins/file_upload/public/get_file_upload_component.ts rename to x-pack/plugins/maps_file_upload/public/get_file_upload_component.ts diff --git a/x-pack/plugins/file_upload/public/index.ts b/x-pack/plugins/maps_file_upload/public/index.ts similarity index 100% rename from x-pack/plugins/file_upload/public/index.ts rename to x-pack/plugins/maps_file_upload/public/index.ts diff --git a/x-pack/plugins/file_upload/public/kibana_services.js b/x-pack/plugins/maps_file_upload/public/kibana_services.js similarity index 100% rename from x-pack/plugins/file_upload/public/kibana_services.js rename to x-pack/plugins/maps_file_upload/public/kibana_services.js diff --git a/x-pack/plugins/file_upload/public/plugin.ts b/x-pack/plugins/maps_file_upload/public/plugin.ts similarity index 100% rename from x-pack/plugins/file_upload/public/plugin.ts rename to x-pack/plugins/maps_file_upload/public/plugin.ts diff --git a/x-pack/plugins/file_upload/public/util/file_parser.js b/x-pack/plugins/maps_file_upload/public/util/file_parser.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/file_parser.js rename to x-pack/plugins/maps_file_upload/public/util/file_parser.js diff --git a/x-pack/plugins/file_upload/public/util/file_parser.test.js b/x-pack/plugins/maps_file_upload/public/util/file_parser.test.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/file_parser.test.js rename to x-pack/plugins/maps_file_upload/public/util/file_parser.test.js diff --git a/x-pack/plugins/file_upload/public/util/geo_json_clean_and_validate.js b/x-pack/plugins/maps_file_upload/public/util/geo_json_clean_and_validate.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/geo_json_clean_and_validate.js rename to x-pack/plugins/maps_file_upload/public/util/geo_json_clean_and_validate.js diff --git a/x-pack/plugins/file_upload/public/util/geo_json_clean_and_validate.test.js b/x-pack/plugins/maps_file_upload/public/util/geo_json_clean_and_validate.test.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/geo_json_clean_and_validate.test.js rename to x-pack/plugins/maps_file_upload/public/util/geo_json_clean_and_validate.test.js diff --git a/x-pack/plugins/file_upload/public/util/geo_processing.js b/x-pack/plugins/maps_file_upload/public/util/geo_processing.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/geo_processing.js rename to x-pack/plugins/maps_file_upload/public/util/geo_processing.js diff --git a/x-pack/plugins/file_upload/public/util/geo_processing.test.js b/x-pack/plugins/maps_file_upload/public/util/geo_processing.test.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/geo_processing.test.js rename to x-pack/plugins/maps_file_upload/public/util/geo_processing.test.js diff --git a/x-pack/plugins/file_upload/public/util/http_service.js b/x-pack/plugins/maps_file_upload/public/util/http_service.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/http_service.js rename to x-pack/plugins/maps_file_upload/public/util/http_service.js diff --git a/x-pack/plugins/file_upload/public/util/indexing_service.js b/x-pack/plugins/maps_file_upload/public/util/indexing_service.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/indexing_service.js rename to x-pack/plugins/maps_file_upload/public/util/indexing_service.js diff --git a/x-pack/plugins/file_upload/public/util/indexing_service.test.js b/x-pack/plugins/maps_file_upload/public/util/indexing_service.test.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/indexing_service.test.js rename to x-pack/plugins/maps_file_upload/public/util/indexing_service.test.js diff --git a/x-pack/plugins/file_upload/public/util/size_limited_chunking.js b/x-pack/plugins/maps_file_upload/public/util/size_limited_chunking.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/size_limited_chunking.js rename to x-pack/plugins/maps_file_upload/public/util/size_limited_chunking.js diff --git a/x-pack/plugins/file_upload/public/util/size_limited_chunking.test.js b/x-pack/plugins/maps_file_upload/public/util/size_limited_chunking.test.js similarity index 100% rename from x-pack/plugins/file_upload/public/util/size_limited_chunking.test.js rename to x-pack/plugins/maps_file_upload/public/util/size_limited_chunking.test.js diff --git a/x-pack/plugins/file_upload/server/client/errors.js b/x-pack/plugins/maps_file_upload/server/client/errors.js similarity index 100% rename from x-pack/plugins/file_upload/server/client/errors.js rename to x-pack/plugins/maps_file_upload/server/client/errors.js diff --git a/x-pack/plugins/file_upload/server/index.js b/x-pack/plugins/maps_file_upload/server/index.js similarity index 100% rename from x-pack/plugins/file_upload/server/index.js rename to x-pack/plugins/maps_file_upload/server/index.js diff --git a/x-pack/plugins/file_upload/server/kibana_server_services.js b/x-pack/plugins/maps_file_upload/server/kibana_server_services.js similarity index 100% rename from x-pack/plugins/file_upload/server/kibana_server_services.js rename to x-pack/plugins/maps_file_upload/server/kibana_server_services.js diff --git a/x-pack/plugins/file_upload/server/models/import_data/import_data.js b/x-pack/plugins/maps_file_upload/server/models/import_data/import_data.js similarity index 100% rename from x-pack/plugins/file_upload/server/models/import_data/import_data.js rename to x-pack/plugins/maps_file_upload/server/models/import_data/import_data.js diff --git a/x-pack/plugins/file_upload/server/models/import_data/index.js b/x-pack/plugins/maps_file_upload/server/models/import_data/index.js similarity index 100% rename from x-pack/plugins/file_upload/server/models/import_data/index.js rename to x-pack/plugins/maps_file_upload/server/models/import_data/index.js diff --git a/x-pack/plugins/file_upload/server/plugin.js b/x-pack/plugins/maps_file_upload/server/plugin.js similarity index 100% rename from x-pack/plugins/file_upload/server/plugin.js rename to x-pack/plugins/maps_file_upload/server/plugin.js diff --git a/x-pack/plugins/file_upload/server/routes/file_upload.js b/x-pack/plugins/maps_file_upload/server/routes/file_upload.js similarity index 100% rename from x-pack/plugins/file_upload/server/routes/file_upload.js rename to x-pack/plugins/maps_file_upload/server/routes/file_upload.js diff --git a/x-pack/plugins/file_upload/server/routes/file_upload.test.js b/x-pack/plugins/maps_file_upload/server/routes/file_upload.test.js similarity index 100% rename from x-pack/plugins/file_upload/server/routes/file_upload.test.js rename to x-pack/plugins/maps_file_upload/server/routes/file_upload.test.js diff --git a/x-pack/plugins/file_upload/server/telemetry/file_upload_usage_collector.ts b/x-pack/plugins/maps_file_upload/server/telemetry/file_upload_usage_collector.ts similarity index 100% rename from x-pack/plugins/file_upload/server/telemetry/file_upload_usage_collector.ts rename to x-pack/plugins/maps_file_upload/server/telemetry/file_upload_usage_collector.ts diff --git a/x-pack/plugins/file_upload/server/telemetry/index.ts b/x-pack/plugins/maps_file_upload/server/telemetry/index.ts similarity index 100% rename from x-pack/plugins/file_upload/server/telemetry/index.ts rename to x-pack/plugins/maps_file_upload/server/telemetry/index.ts diff --git a/x-pack/plugins/file_upload/server/telemetry/mappings.ts b/x-pack/plugins/maps_file_upload/server/telemetry/mappings.ts similarity index 100% rename from x-pack/plugins/file_upload/server/telemetry/mappings.ts rename to x-pack/plugins/maps_file_upload/server/telemetry/mappings.ts diff --git a/x-pack/plugins/file_upload/server/telemetry/telemetry.test.ts b/x-pack/plugins/maps_file_upload/server/telemetry/telemetry.test.ts similarity index 100% rename from x-pack/plugins/file_upload/server/telemetry/telemetry.test.ts rename to x-pack/plugins/maps_file_upload/server/telemetry/telemetry.test.ts diff --git a/x-pack/plugins/file_upload/server/telemetry/telemetry.ts b/x-pack/plugins/maps_file_upload/server/telemetry/telemetry.ts similarity index 100% rename from x-pack/plugins/file_upload/server/telemetry/telemetry.ts rename to x-pack/plugins/maps_file_upload/server/telemetry/telemetry.ts From 46285b2257c5ad11add7412e4145d842e3f91b83 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 26 Jan 2021 22:34:43 +0100 Subject: [PATCH 04/16] [ILM] Timeline component (#88024) * cleaning up unused types and legacy logic * added new relative age logic with unit tests * initial implementation of timeline * added custom infinity icon to timeline component * added comment * move timeline color bar comment * fix nanoseconds and microsecnds bug * added policy timeline heading, removed "at least" copy for now * a few minor changes - fix up copy - fix up responsive/mobile first view of timeline - adjust minimum size of a color bar * minor refactor to css classnames and make trash can for delete more prominent * added delete icon tooltip with rough first copy * added smoke test for timeline and how it interacts with different policy states * update test and copy * convert string svg to react svg component and use euiIcon class and refactor scss * update delete icon tooltip copy and add aria-label to svg * mock EuiIcon component in jest tests because it causes issues with custom react-svg component * added comment to mock * remove setting of classname * fix typo and update delete icon tooltip content * refinements to the delete icon at the end of the timline and support dark mode Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../client_integration/app/app.test.ts | 9 + .../edit_policy/edit_policy.helpers.tsx | 7 + .../edit_policy/edit_policy.test.ts | 46 ++++ .../__jest__/components/edit_policy.test.tsx | 9 + .../common/types/policies.ts | 2 + .../sections/edit_policy/components/index.ts | 1 + .../edit_policy/components/timeline/index.ts | 6 + .../components/timeline/infinity_icon.svg.tsx | 26 +++ .../components/timeline/timeline.scss | 87 ++++++++ .../components/timeline/timeline.tsx | 208 ++++++++++++++++++ .../sections/edit_policy/edit_policy.tsx | 7 +- 11 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/index.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/infinity_icon.svg.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.scss create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.tsx diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts index 9052cf2847baa..1ffbae39d3705 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts @@ -23,6 +23,15 @@ const PERCENT_SIGN_25_SEQUENCE = 'test%25'; window.scrollTo = jest.fn(); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + + return { + ...original, + EuiIcon: 'eui-icon', // using custom react-svg icon causes issues, mocking for now. + }; +}); + describe('', () => { let testBed: AppTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index 7206fbfd547d4..72a0372628a22 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -41,6 +41,7 @@ jest.mock('@elastic/eui', () => { }} /> ), + EuiIcon: 'eui-icon', // using custom react-svg icon causes issues, mocking for now. }; }); @@ -236,6 +237,12 @@ export const setup = async (arg?: { appServicesContext: Partial exists('ilmTimelineHotPhase'), + hasWarmPhase: () => exists('ilmTimelineWarmPhase'), + hasColdPhase: () => exists('ilmTimelineColdPhase'), + hasDeletePhase: () => exists('ilmTimelineDeletePhase'), + }, hot: { setMaxSize, setMaxDocs, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index ff070a7f08bb1..e42e503fb853a 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -824,4 +824,50 @@ describe('', () => { expect(actions.cold.searchableSnapshotDisabledDueToRollover()).toBeTruthy(); }); }); + + describe('policy timeline', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: {}, + nodesByAttributes: { test: ['123'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + httpRequestsMockHelpers.setLoadSnapshotPolicies([]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + test('showing all phases on the timeline', async () => { + const { actions } = testBed; + // This is how the default policy should look + expect(actions.timeline.hasHotPhase()).toBe(true); + expect(actions.timeline.hasWarmPhase()).toBe(false); + expect(actions.timeline.hasColdPhase()).toBe(false); + expect(actions.timeline.hasDeletePhase()).toBe(false); + + await actions.warm.enable(true); + expect(actions.timeline.hasHotPhase()).toBe(true); + expect(actions.timeline.hasWarmPhase()).toBe(true); + expect(actions.timeline.hasColdPhase()).toBe(false); + expect(actions.timeline.hasDeletePhase()).toBe(false); + + await actions.cold.enable(true); + expect(actions.timeline.hasHotPhase()).toBe(true); + expect(actions.timeline.hasWarmPhase()).toBe(true); + expect(actions.timeline.hasColdPhase()).toBe(true); + expect(actions.timeline.hasDeletePhase()).toBe(false); + + await actions.delete.enable(true); + expect(actions.timeline.hasHotPhase()).toBe(true); + expect(actions.timeline.hasWarmPhase()).toBe(true); + expect(actions.timeline.hasColdPhase()).toBe(true); + expect(actions.timeline.hasDeletePhase()).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx index c54ccb9f85edf..6aa6c3177ca5d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx @@ -81,6 +81,15 @@ for (let i = 0; i < 105; i++) { } window.scrollTo = jest.fn(); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + + return { + ...original, + EuiIcon: 'eui-icon', // using custom react-svg icon causes issues, mocking for now. + }; +}); + let component: ReactElement; const activatePhase = async (rendered: ReactWrapper, phase: string) => { const testSubject = `enablePhaseSwitch-${phase}`; diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 1f4b06e80c49f..a084f16226fda 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -20,6 +20,8 @@ export interface Phases { delete?: SerializedDeletePhase; } +export type PhasesExceptDelete = keyof Omit; + export interface PolicyFromES { modified_date: string; name: string; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index fa550214db477..d22206d7ae4de 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -11,5 +11,6 @@ export { OptionalLabel } from './optional_label'; export { PolicyJsonFlyout } from './policy_json_flyout'; export { DescribedFormRow, ToggleFieldWithDescribedFormRow } from './described_form_row'; export { FieldLoadingError } from './field_loading_error'; +export { Timeline } from './timeline'; export * from './phases'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/index.ts new file mode 100644 index 0000000000000..4664429db37d7 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/index.ts @@ -0,0 +1,6 @@ +/* + * 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 { Timeline } from './timeline'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/infinity_icon.svg.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/infinity_icon.svg.tsx new file mode 100644 index 0000000000000..b3b1b3cc56b3d --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/infinity_icon.svg.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; + +export const InfinityIconSvg: FunctionComponent = (props) => { + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.scss new file mode 100644 index 0000000000000..452221a29a991 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.scss @@ -0,0 +1,87 @@ +$ilmTimelineBarHeight: $euiSizeS; + +/* +* For theming we need to shade or tint to get the right color from the base EUI color +*/ +$ilmDeletePhaseBackgroundColor: tintOrShade($euiColorVis5_behindText, 80%,80%); +$ilmDeletePhaseColor: shadeOrTint($euiColorVis5, 40%, 40%); + +.ilmTimeline { + overflow: hidden; + width: 100%; + + &__phasesContainer { + /* + * Let the delete icon sit on the same line as the phase color bars + */ + display: inline-block; + width: 100%; + + &__phase:first-child { + padding-left: 0; + padding-right: $euiSizeS; + } + + &__phase:last-child { + padding-left: $euiSizeS; + padding-right: 0; + } + + &__phase:only-child { + padding-left: 0; + padding-right: 0; + } + + &__phase { + /* + * Let the phase color bars sit horizontally + */ + display: inline-block; + + padding-left: $euiSizeS; + padding-right: $euiSizeS; + } + } + + &__deleteIconContainer { + /* + * Create a bit of space between the timeline and the delete icon + */ + padding: $euiSizeM; + margin-left: $euiSizeM; + background-color: $ilmDeletePhaseBackgroundColor; + color: $ilmDeletePhaseColor; + border-radius: calc(#{$euiSizeS} / 2); + } + + &__colorBar { + display: inline-block; + height: $ilmTimelineBarHeight; + border-radius: calc(#{$ilmTimelineBarHeight} / 2); + width: 100%; + } + + &__hotPhase { + width: var(--ilm-timeline-hot-phase-width); + + &__colorBar { + background-color: $euiColorVis9; + } + } + + &__warmPhase { + width: var(--ilm-timeline-warm-phase-width); + + &__colorBar { + background-color: $euiColorVis5; + } + } + + &__coldPhase { + width: var(--ilm-timeline-cold-phase-width); + + &__colorBar { + background-color: $euiColorVis1; + } + } +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.tsx new file mode 100644 index 0000000000000..40bab9c676de2 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.tsx @@ -0,0 +1,208 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { FunctionComponent, useMemo } from 'react'; +import { + EuiText, + EuiIcon, + EuiIconProps, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiIconTip, +} from '@elastic/eui'; + +import { PhasesExceptDelete } from '../../../../../../common/types'; +import { useFormData } from '../../../../../shared_imports'; + +import { FormInternal } from '../../types'; + +import { + calculateRelativeTimingMs, + normalizeTimingsToHumanReadable, + PhaseAgeInMilliseconds, +} from '../../lib'; + +import './timeline.scss'; +import { InfinityIconSvg } from './infinity_icon.svg'; + +const InfinityIcon: FunctionComponent> = (props) => ( + +); + +const toPercent = (n: number, total: number) => (n / total) * 100; + +const msTimeToOverallPercent = (ms: number, totalMs: number) => { + if (!isFinite(ms)) { + return 100; + } + if (totalMs === 0) { + return 100; + } + return toPercent(ms, totalMs); +}; + +/** + * Each phase, if active, should have a minimum width it occupies. The higher this + * base amount, the smaller the variance in phase size in the timeline. This functions + * as a min-width constraint. + */ +const SCORE_BUFFER_AMOUNT = 50; + +const i18nTexts = { + hotPhase: i18n.translate('xpack.indexLifecycleMgmt.timeline.hotPhaseSectionTitle', { + defaultMessage: 'Hot phase', + }), + warmPhase: i18n.translate('xpack.indexLifecycleMgmt.timeline.warmPhaseSectionTitle', { + defaultMessage: 'Warm phase', + }), + coldPhase: i18n.translate('xpack.indexLifecycleMgmt.timeline.coldPhaseSectionTitle', { + defaultMessage: 'Cold phase', + }), + deleteIcon: { + toolTipContent: i18n.translate('xpack.indexLifecycleMgmt.timeline.deleteIconToolTipContent', { + defaultMessage: 'Policy deletes the index after lifecycle phases complete.', + }), + }, +}; + +const calculateWidths = (inputs: PhaseAgeInMilliseconds) => { + const hotScore = msTimeToOverallPercent(inputs.phases.hot, inputs.total) + SCORE_BUFFER_AMOUNT; + const warmScore = + inputs.phases.warm != null + ? msTimeToOverallPercent(inputs.phases.warm, inputs.total) + SCORE_BUFFER_AMOUNT + : 0; + const coldScore = + inputs.phases.cold != null + ? msTimeToOverallPercent(inputs.phases.cold, inputs.total) + SCORE_BUFFER_AMOUNT + : 0; + + const totalScore = hotScore + warmScore + coldScore; + return { + hot: `${toPercent(hotScore, totalScore)}%`, + warm: `${toPercent(warmScore, totalScore)}%`, + cold: `${toPercent(coldScore, totalScore)}%`, + }; +}; + +const TimelinePhaseText: FunctionComponent<{ + phaseName: string; + durationInPhase?: React.ReactNode | string; +}> = ({ phaseName, durationInPhase }) => ( + + + + {phaseName} + + + + {typeof durationInPhase === 'string' ? ( + {durationInPhase} + ) : ( + durationInPhase + )} + + +); + +export const Timeline: FunctionComponent = () => { + const [formData] = useFormData(); + + const phaseTimingInMs = useMemo(() => { + return calculateRelativeTimingMs(formData); + }, [formData]); + + const humanReadableTimings = useMemo(() => normalizeTimingsToHumanReadable(phaseTimingInMs), [ + phaseTimingInMs, + ]); + + const widths = calculateWidths(phaseTimingInMs); + + const getDurationInPhaseContent = (phase: PhasesExceptDelete): string | React.ReactNode => + phaseTimingInMs.phases[phase] === Infinity ? ( + + ) : ( + humanReadableTimings[phase] + ); + + return ( + + + +

+ {i18n.translate('xpack.indexLifecycleMgmt.timeline.title', { + defaultMessage: 'Policy Timeline', + })} +

+
+
+ +
{ + if (el) { + el.style.setProperty('--ilm-timeline-hot-phase-width', widths.hot); + el.style.setProperty('--ilm-timeline-warm-phase-width', widths.warm ?? null); + el.style.setProperty('--ilm-timeline-cold-phase-width', widths.cold ?? null); + } + }} + > + + +
+ {/* These are the actual color bars for the timeline */} +
+
+ +
+ {formData._meta?.warm.enabled && ( +
+
+ +
+ )} + {formData._meta?.cold.enabled && ( +
+
+ +
+ )} +
+ + {formData._meta?.delete.enabled && ( + +
+ +
+
+ )} + +
+ + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index d945ae8bb3e4e..228a0f9fdb942 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -43,6 +43,7 @@ import { DeletePhase, HotPhase, WarmPhase, + Timeline, } from './components'; import { schema, deserializer, createSerializer, createPolicyNameValidations, Form } from './form'; @@ -256,7 +257,11 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { ) : null} - + + + + + From 1e7b6f0ec25974641f3d8e96f5257ba8c518d515 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 26 Jan 2021 13:42:27 -0800 Subject: [PATCH 05/16] skip flaky suite (#88826) --- test/functional/apps/home/_navigation.ts | 80 +++++++++++++----------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/test/functional/apps/home/_navigation.ts b/test/functional/apps/home/_navigation.ts index c90398fa84afc..90d13b4b5e417 100644 --- a/test/functional/apps/home/_navigation.ts +++ b/test/functional/apps/home/_navigation.ts @@ -15,42 +15,46 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const appsMenu = getService('appsMenu'); const esArchiver = getService('esArchiver'); - describe('Kibana browser back navigation should work', function describeIndexTests() { - before(async () => { - await esArchiver.loadIfNeeded('discover'); - await esArchiver.loadIfNeeded('logstash_functional'); - }); - - it('detect navigate back issues', async () => { - let currUrl; - // Detects bug described in issue #31238 - where back navigation would get stuck to URL encoding handling in Angular. - // Navigate to home app - await PageObjects.common.navigateToApp('home'); - const homeUrl = await browser.getCurrentUrl(); - - // Navigate to discover app - await appsMenu.clickLink('Discover'); - const discoverUrl = await browser.getCurrentUrl(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - const modifiedTimeDiscoverUrl = await browser.getCurrentUrl(); - - // Navigate to dashboard app - await appsMenu.clickLink('Dashboard'); - - // Navigating back to discover - await browser.goBack(); - currUrl = await browser.getCurrentUrl(); - expect(currUrl).to.be(modifiedTimeDiscoverUrl); - - // Navigating back from time settings - await browser.goBack(); // undo time settings - currUrl = await browser.getCurrentUrl(); - expect(currUrl.startsWith(discoverUrl)).to.be(true); - - // Navigate back home - await browser.goBack(); - currUrl = await browser.getCurrentUrl(); - expect(currUrl).to.be(homeUrl); - }); - }); + // Failing: See https://github.com/elastic/kibana/issues/88826 + describe.skip( + 'Kibana browser back navigation should work', + function describeIndexTests() { + before(async () => { + await esArchiver.loadIfNeeded('discover'); + await esArchiver.loadIfNeeded('logstash_functional'); + }); + + it('detect navigate back issues', async () => { + let currUrl; + // Detects bug described in issue #31238 - where back navigation would get stuck to URL encoding handling in Angular. + // Navigate to home app + await PageObjects.common.navigateToApp('home'); + const homeUrl = await browser.getCurrentUrl(); + + // Navigate to discover app + await appsMenu.clickLink('Discover'); + const discoverUrl = await browser.getCurrentUrl(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + const modifiedTimeDiscoverUrl = await browser.getCurrentUrl(); + + // Navigate to dashboard app + await appsMenu.clickLink('Dashboard'); + + // Navigating back to discover + await browser.goBack(); + currUrl = await browser.getCurrentUrl(); + expect(currUrl).to.be(modifiedTimeDiscoverUrl); + + // Navigating back from time settings + await browser.goBack(); // undo time settings + currUrl = await browser.getCurrentUrl(); + expect(currUrl.startsWith(discoverUrl)).to.be(true); + + // Navigate back home + await browser.goBack(); + currUrl = await browser.getCurrentUrl(); + expect(currUrl).to.be(homeUrl); + }); + } + ); } From 2a9a9305ee4cf6b79b917ff6c8d76575af739aff Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 26 Jan 2021 14:14:08 -0800 Subject: [PATCH 06/16] Fixes linting caused by skipping flaky suite Signed-off-by: Tyler Smalley --- test/functional/apps/home/_navigation.ts | 79 ++++++++++++------------ 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/test/functional/apps/home/_navigation.ts b/test/functional/apps/home/_navigation.ts index 90d13b4b5e417..12f97a5349419 100644 --- a/test/functional/apps/home/_navigation.ts +++ b/test/functional/apps/home/_navigation.ts @@ -16,45 +16,42 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); // Failing: See https://github.com/elastic/kibana/issues/88826 - describe.skip( - 'Kibana browser back navigation should work', - function describeIndexTests() { - before(async () => { - await esArchiver.loadIfNeeded('discover'); - await esArchiver.loadIfNeeded('logstash_functional'); - }); - - it('detect navigate back issues', async () => { - let currUrl; - // Detects bug described in issue #31238 - where back navigation would get stuck to URL encoding handling in Angular. - // Navigate to home app - await PageObjects.common.navigateToApp('home'); - const homeUrl = await browser.getCurrentUrl(); - - // Navigate to discover app - await appsMenu.clickLink('Discover'); - const discoverUrl = await browser.getCurrentUrl(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - const modifiedTimeDiscoverUrl = await browser.getCurrentUrl(); - - // Navigate to dashboard app - await appsMenu.clickLink('Dashboard'); - - // Navigating back to discover - await browser.goBack(); - currUrl = await browser.getCurrentUrl(); - expect(currUrl).to.be(modifiedTimeDiscoverUrl); - - // Navigating back from time settings - await browser.goBack(); // undo time settings - currUrl = await browser.getCurrentUrl(); - expect(currUrl.startsWith(discoverUrl)).to.be(true); - - // Navigate back home - await browser.goBack(); - currUrl = await browser.getCurrentUrl(); - expect(currUrl).to.be(homeUrl); - }); - } - ); + describe.skip('Kibana browser back navigation should work', function describeIndexTests() { + before(async () => { + await esArchiver.loadIfNeeded('discover'); + await esArchiver.loadIfNeeded('logstash_functional'); + }); + + it('detect navigate back issues', async () => { + let currUrl; + // Detects bug described in issue #31238 - where back navigation would get stuck to URL encoding handling in Angular. + // Navigate to home app + await PageObjects.common.navigateToApp('home'); + const homeUrl = await browser.getCurrentUrl(); + + // Navigate to discover app + await appsMenu.clickLink('Discover'); + const discoverUrl = await browser.getCurrentUrl(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + const modifiedTimeDiscoverUrl = await browser.getCurrentUrl(); + + // Navigate to dashboard app + await appsMenu.clickLink('Dashboard'); + + // Navigating back to discover + await browser.goBack(); + currUrl = await browser.getCurrentUrl(); + expect(currUrl).to.be(modifiedTimeDiscoverUrl); + + // Navigating back from time settings + await browser.goBack(); // undo time settings + currUrl = await browser.getCurrentUrl(); + expect(currUrl.startsWith(discoverUrl)).to.be(true); + + // Navigate back home + await browser.goBack(); + currUrl = await browser.getCurrentUrl(); + expect(currUrl).to.be(homeUrl); + }); + }); } From 20f32d506fd72f567eda678c90a67a4ff9e75f2c Mon Sep 17 00:00:00 2001 From: Rashmi Kulkarni Date: Tue, 26 Jan 2021 14:20:42 -0800 Subject: [PATCH 07/16] JSON Body payload for the webhook connector in Alerts & Actions (#89253) * fixes https://github.com/elastic/kibana/issues/74449 Co-authored-by: Patrick Mueller --- docs/user/alerting/action-types/webhook.asciidoc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/user/alerting/action-types/webhook.asciidoc b/docs/user/alerting/action-types/webhook.asciidoc index 659c3afad6bd1..fff6814325ea4 100644 --- a/docs/user/alerting/action-types/webhook.asciidoc +++ b/docs/user/alerting/action-types/webhook.asciidoc @@ -72,4 +72,17 @@ Password:: An optional password. If set, HTTP basic authentication is used. Cur Webhook actions have the following properties: -Body:: A json payload sent to the request URL. +Body:: A JSON payload sent to the request URL. For example: ++ +[source,text] +-- +{ + "short_description": "{{context.rule.name}}", + "description": "{{context.rule.description}}", + ... +} +-- + +Mustache template variables (the text enclosed in double braces, for example, `context.rule.name`) have +their values escaped, so that the final JSON will be valid (escaping double quote characters). +For more information on Mustache template variables, refer to <>. From b5b9cee1584997a480d6a2a2b5edafcf3fcf9976 Mon Sep 17 00:00:00 2001 From: Justin Ibarra Date: Tue, 26 Jan 2021 13:43:05 -0900 Subject: [PATCH 08/16] Update security solution generic timeline templates (#89239) * Update security solution generic timeline templates --- .../rules/prepackaged_timelines/endpoint.json | 2 +- .../rules/prepackaged_timelines/index.ndjson | 6 +++--- .../rules/prepackaged_timelines/network.json | 2 +- .../rules/prepackaged_timelines/process.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json index 711050e1f136a..acc5f69358798 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json @@ -1 +1 @@ -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":"{agent.type}","field":"agent.type","displayField":"agent.type","value":"{agent.type}","operator":":*"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1594736314036,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1611609999115,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson index 7c074242c39d1..522430205c25a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson @@ -7,6 +7,6 @@ // Auto generated file from scripts/regen_prepackage_timelines_index.sh // Do not hand edit. Run that script to regenerate package information instead -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":"{agent.type}","field":"agent.type","displayField":"agent.type","value":"{agent.type}","operator":":*"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1594736314036,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{source.ip}","queryMatch":{"displayValue":null,"field":"source.ip","displayField":null,"value":"{source.ip}","operator":":*"},"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayValue":null,"field":"destination.ip","displayField":null,"value":"{destination.ip}","operator":":*"},"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1594736099397,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"{event.type}","queryMatch":{"displayValue":null,"field":"event.type","displayField":null,"value":"{event.type}","operator":":*"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1594736083598,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1611609999115,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"and":[{"enabled":true,"excluded":false,"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayField":"destination.ip","displayValue":"{destination.ip}","field":"destination.ip","operator":":","value":"{destination.ip}"},"type":"template"}],"enabled":true,"excluded":false,"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","kqlQuery":"","name":"{source.ip}","queryMatch":{"displayField":"source.ip","displayValue":"{source.ip}","field":"source.ip","operator":":","value":"{source.ip}"},"type":"template"}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1611609960850,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1611609848602,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json index 1634476b4e99d..6e93387579d22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json @@ -1 +1 @@ -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{source.ip}","queryMatch":{"displayValue":null,"field":"source.ip","displayField":null,"value":"{source.ip}","operator":":*"},"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayValue":null,"field":"destination.ip","displayField":null,"value":"{destination.ip}","operator":":*"},"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1594736099397,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"and":[{"enabled":true,"excluded":false,"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayField":"destination.ip","displayValue":"{destination.ip}","field":"destination.ip","operator":":","value":"{destination.ip}"},"type":"template"}],"enabled":true,"excluded":false,"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","kqlQuery":"","name":"{source.ip}","queryMatch":{"displayField":"source.ip","displayValue":"{source.ip}","field":"source.ip","operator":":","value":"{source.ip}"},"type":"template"}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1611609960850,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json index 767f38133f263..c25873746a9e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json @@ -1 +1 @@ -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"{event.type}","queryMatch":{"displayValue":null,"field":"event.type","displayField":null,"value":"{event.type}","operator":":*"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1594736083598,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1611609848602,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} From 0fe7b9e080c67c43aefdb7ea25d5e90a80cb4ade Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 26 Jan 2021 15:27:48 -0800 Subject: [PATCH 09/16] skip flaky suite (#89031) --- test/functional/apps/management/_test_huge_fields.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/management/_test_huge_fields.js b/test/functional/apps/management/_test_huge_fields.js index ca95af8cb4205..2ab619276d2b9 100644 --- a/test/functional/apps/management/_test_huge_fields.js +++ b/test/functional/apps/management/_test_huge_fields.js @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }) { const security = getService('security'); const PageObjects = getPageObjects(['common', 'home', 'settings']); - describe('test large number of fields', function () { + // Failing: See https://github.com/elastic/kibana/issues/89031 + describe.skip('test large number of fields', function () { this.tags(['skipCloud']); const EXPECTED_FIELD_COUNT = '10006'; From 2c648acffda7c05cac2949eb5994c0f2066aa12b Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 26 Jan 2021 16:30:52 -0800 Subject: [PATCH 10/16] skip flaky suite (#89379) --- test/functional/apps/home/_sample_data.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/home/_sample_data.ts b/test/functional/apps/home/_sample_data.ts index a9fe2026112b6..438dd6f8adce2 100644 --- a/test/functional/apps/home/_sample_data.ts +++ b/test/functional/apps/home/_sample_data.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardExpect = getService('dashboardExpect'); const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'timePicker']); - describe('sample data', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/89379 + describe.skip('sample data', function describeIndexTests() { before(async () => { await security.testUser.setRoles(['kibana_admin', 'kibana_sample_admin']); await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { From 8250b078b4b47415435ee72fb9b04908f99c6ed5 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 27 Jan 2021 00:59:24 +0000 Subject: [PATCH 11/16] chore(NA): improve ts build refs performance on kbn bootstrap (#89333) * chore(NA): improve ts build refs performance on kbn bootstrap * chore(NA): use skipLibCheck=false by default on typechecking * docs(NA): commit core docs changes * fix(NA): eui typings --- ...lugin-core-server.kibanaresponsefactory.md | 8 +- ...ns-data-public.aggconfigs._constructor_.md | 6 +- ...na-plugin-plugins-data-public.searchbar.md | 4 +- ...plugin-plugins-data-server.plugin.start.md | 4 +- package.json | 10 +- src/core/server/server.api.md | 8 +- src/dev/typescript/run_type_check_cli.ts | 6 +- src/plugins/data/public/public.api.md | 8 +- src/plugins/data/server/server.api.md | 2 +- tsconfig.base.json | 2 + typings/@elastic/eui/index.d.ts | 10 +- typings/@elastic/eui/lib/format.d.ts | 9 -- typings/@elastic/eui/lib/services.d.ts | 9 -- yarn.lock | 119 +++++++++--------- 14 files changed, 99 insertions(+), 106 deletions(-) delete mode 100644 typings/@elastic/eui/lib/format.d.ts delete mode 100644 typings/@elastic/eui/lib/services.d.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md index b488baacaff25..d7eafdce017e4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md @@ -10,7 +10,7 @@ Set of helpers used to create `KibanaResponse` to form HTTP response on an incom ```typescript kibanaResponseFactory: { - custom: | { + custom: | Buffer | Error | Stream | { message: string | Error; attributes?: Record | undefined; } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; @@ -21,9 +21,9 @@ kibanaResponseFactory: { conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; - redirected: (options: RedirectResponseOptions) => KibanaResponse>; - ok: (options?: HttpResponseOptions) => KibanaResponse>; - accepted: (options?: HttpResponseOptions) => KibanaResponse>; + redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; + ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; noContent: (options?: HttpResponseOptions) => KibanaResponse; } ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md index c9e08b9712480..6ca7a1a88b30e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md @@ -15,11 +15,11 @@ constructor(indexPattern: IndexPattern, configStates: Pick & Pick<{ + }, "schema" | "enabled" | "id" | "params"> & Pick<{ type: string | IAggType; }, "type"> & Pick<{ type: string | IAggType; - }, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined, opts: AggConfigsOptions); + }, never>, "schema" | "type" | "enabled" | "id" | "params">[] | undefined, opts: AggConfigsOptions); ``` ## Parameters @@ -27,6 +27,6 @@ constructor(indexPattern: IndexPattern, configStates: PickIndexPattern | | -| configStates | Pick<Pick<{
type: string;
enabled?: boolean | undefined;
id?: string | undefined;
params?: {} | import("./agg_config").SerializableState | undefined;
schema?: string | undefined;
}, "enabled" | "schema" | "id" | "params"> & Pick<{
type: string | IAggType;
}, "type"> & Pick<{
type: string | IAggType;
}, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined | | +| configStates | Pick<Pick<{
type: string;
enabled?: boolean | undefined;
id?: string | undefined;
params?: {} | import("./agg_config").SerializableState | undefined;
schema?: string | undefined;
}, "schema" | "enabled" | "id" | "params"> & Pick<{
type: string | IAggType;
}, "type"> & Pick<{
type: string | IAggType;
}, never>, "schema" | "type" | "enabled" | "id" | "params">[] | undefined | | | opts | AggConfigsOptions | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index 2fd84730957b6..83fbc00860ca5 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "indexPatterns" | "filters" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index af7abb076d7ef..ea3ba28a52def 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/package.json b/package.json index d14c5b0a7dc5f..42949a7014131 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "**/react-syntax-highlighter/**/highlight.js": "^10.4.1", "**/request": "^2.88.2", "**/trim": "0.0.3", - "**/typescript": "4.1.2" + "**/typescript": "4.1.3" }, "engines": { "node": "14.15.4", @@ -561,8 +561,8 @@ "@types/xml2js": "^0.4.5", "@types/yauzl": "^2.9.1", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^4.8.1", - "@typescript-eslint/parser": "^4.8.1", + "@typescript-eslint/eslint-plugin": "^4.14.1", + "@typescript-eslint/parser": "^4.14.1", "@welldone-software/why-did-you-render": "^5.0.0", "@yarnpkg/lockfile": "^1.1.0", "abab": "^2.0.4", @@ -820,9 +820,9 @@ "topojson-client": "3.0.0", "ts-loader": "^7.0.5", "tsd": "^0.13.1", - "typescript": "4.1.2", + "typescript": "4.1.3", "typescript-fsa": "^3.0.0", - "typescript-fsa-reducers": "^1.2.1", + "typescript-fsa-reducers": "^1.2.2", "unlazy-loader": "^0.1.3", "unstated": "^2.1.1", "url-loader": "^2.2.0", diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index ceab69a6cdb18..0b058011267eb 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1256,7 +1256,7 @@ export type KibanaResponseFactory = typeof kibanaResponseFactory; // @public export const kibanaResponseFactory: { - custom: | { + custom: | Buffer | Error | Stream | { message: string | Error; attributes?: Record | undefined; } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; @@ -1267,9 +1267,9 @@ export const kibanaResponseFactory: { conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; - redirected: (options: RedirectResponseOptions) => KibanaResponse>; - ok: (options?: HttpResponseOptions) => KibanaResponse>; - accepted: (options?: HttpResponseOptions) => KibanaResponse>; + redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; + ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; noContent: (options?: HttpResponseOptions) => KibanaResponse; }; diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index c1a25a8e4bdee..6e7ea6fcf2405 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -61,7 +61,7 @@ export async function runTypeCheckCli() { Options: --project [path] {dim Path to a tsconfig.json file determines the project to check} - --skip-lib-check {dim Skip type checking of all declaration files (*.d.ts)} + --skip-lib-check {dim Skip type checking of all declaration files (*.d.ts). Default is false} --help {dim Show this message} `) ); @@ -77,7 +77,9 @@ export async function runTypeCheckCli() { ...['--emitDeclarationOnly', 'false'], '--noEmit', '--pretty', - ...(opts['skip-lib-check'] ? ['--skipLibCheck'] : []), + ...(opts['skip-lib-check'] + ? ['--skipLibCheck', opts['skip-lib-check']] + : ['--skipLibCheck', 'false']), ]; const projects = filterProjectsByFlag(opts.project).filter((p) => !p.disableTypeCheck); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 28997de4517e7..002f033365790 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -218,11 +218,11 @@ export class AggConfigs { id?: string | undefined; params?: {} | import("./agg_config").SerializableState | undefined; schema?: string | undefined; - }, "enabled" | "schema" | "id" | "params"> & Pick<{ + }, "schema" | "enabled" | "id" | "params"> & Pick<{ type: string | IAggType; }, "type"> & Pick<{ type: string | IAggType; - }, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined, opts: AggConfigsOptions); + }, never>, "schema" | "type" | "enabled" | "id" | "params">[] | undefined, opts: AggConfigsOptions); // (undocumented) aggs: IAggConfig[]; // (undocumented) @@ -2235,8 +2235,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "indexPatterns" | "filters" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" 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 6a96fd8209a8d..9789f3354e9ef 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1138,7 +1138,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; diff --git a/tsconfig.base.json b/tsconfig.base.json index 247813da51cfb..f8e07911e71ce 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,6 +14,8 @@ "strict": true, // save information about the project graph on disk "incremental": true, + // Do not check d.ts files by default + "skipLibCheck": true, // enables "core language features" "lib": [ "esnext", diff --git a/typings/@elastic/eui/index.d.ts b/typings/@elastic/eui/index.d.ts index 74f89608bc04f..e5cf7864a83b2 100644 --- a/typings/@elastic/eui/index.d.ts +++ b/typings/@elastic/eui/index.d.ts @@ -6,6 +6,12 @@ * Public License, v 1. */ -import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; - // TODO: Remove once typescript definitions are in EUI + +declare module '@elastic/eui/lib/services' { + export const RIGHT_ALIGNMENT: any; +} + +declare module '@elastic/eui/lib/services/format' { + export const dateFormatAliases: any; +} diff --git a/typings/@elastic/eui/lib/format.d.ts b/typings/@elastic/eui/lib/format.d.ts deleted file mode 100644 index 4be830ec2d98c..0000000000000 --- a/typings/@elastic/eui/lib/format.d.ts +++ /dev/null @@ -1,9 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export const dateFormatAliases: any; diff --git a/typings/@elastic/eui/lib/services.d.ts b/typings/@elastic/eui/lib/services.d.ts deleted file mode 100644 index a667d111ceb72..0000000000000 --- a/typings/@elastic/eui/lib/services.d.ts +++ /dev/null @@ -1,9 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export const RIGHT_ALIGNMENT: any; diff --git a/yarn.lock b/yarn.lock index 174f1284a3a6b..ed861b58773b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6861,28 +6861,29 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.1.tgz#b362abe0ee478a6c6d06c14552a6497f0b480769" - integrity sha512-d7LeQ7dbUrIv5YVFNzGgaW3IQKMmnmKFneRWagRlGYOSfLJVaRbj/FrBNOBC1a3tVO+TgNq1GbHvRtg1kwL0FQ== +"@typescript-eslint/eslint-plugin@^4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.1.tgz#22dd301ce228aaab3416b14ead10b1db3e7d3180" + integrity sha512-5JriGbYhtqMS1kRcZTQxndz1lKMwwEXKbwZbkUZNnp6MJX0+OVXnG0kOlBZP4LUAxEyzu3cs+EXd/97MJXsGfw== dependencies: - "@typescript-eslint/experimental-utils" "4.8.1" - "@typescript-eslint/scope-manager" "4.8.1" + "@typescript-eslint/experimental-utils" "4.14.1" + "@typescript-eslint/scope-manager" "4.14.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" + lodash "^4.17.15" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.8.1.tgz#27275c20fa4336df99ebcf6195f7d7aa7aa9f22d" - integrity sha512-WigyLn144R3+lGATXW4nNcDJ9JlTkG8YdBWHkDlN0lC3gUGtDi7Pe3h5GPvFKMcRz8KbZpm9FJV9NTW8CpRHpg== +"@typescript-eslint/experimental-utils@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz#a5c945cb24dabb96747180e1cfc8487f8066f471" + integrity sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.8.1" - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/typescript-estree" "4.8.1" + "@typescript-eslint/scope-manager" "4.14.1" + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/typescript-estree" "4.14.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -6898,16 +6899,24 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.8.1.tgz#4fe2fbdbb67485bafc4320b3ae91e34efe1219d1" - integrity sha512-QND8XSVetATHK9y2Ltc/XBl5Ro7Y62YuZKnPEwnNPB8E379fDsvzJ1dMJ46fg/VOmk0hXhatc+GXs5MaXuL5Uw== +"@typescript-eslint/parser@^4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.14.1.tgz#3bd6c24710cd557d8446625284bcc9c6d52817c6" + integrity sha512-mL3+gU18g9JPsHZuKMZ8Z0Ss9YP1S5xYZ7n68Z98GnPq02pYNQuRXL85b9GYhl6jpdvUc45Km7hAl71vybjUmw== dependencies: - "@typescript-eslint/scope-manager" "4.8.1" - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/typescript-estree" "4.8.1" + "@typescript-eslint/scope-manager" "4.14.1" + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/typescript-estree" "4.14.1" debug "^4.1.1" +"@typescript-eslint/scope-manager@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz#8444534254c6f370e9aa974f035ced7fe713ce02" + integrity sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw== + dependencies: + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/visitor-keys" "4.14.1" + "@typescript-eslint/scope-manager@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.3.0.tgz#c743227e087545968080d2362cfb1273842cb6a7" @@ -6916,23 +6925,29 @@ "@typescript-eslint/types" "4.3.0" "@typescript-eslint/visitor-keys" "4.3.0" -"@typescript-eslint/scope-manager@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.8.1.tgz#e343c475f8f1d15801b546cb17d7f309b768fdce" - integrity sha512-r0iUOc41KFFbZdPAdCS4K1mXivnSZqXS5D9oW+iykQsRlTbQRfuFRSW20xKDdYiaCoH+SkSLeIF484g3kWzwOQ== - dependencies: - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/visitor-keys" "4.8.1" +"@typescript-eslint/types@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.14.1.tgz#b3d2eb91dafd0fd8b3fce7c61512ac66bd0364aa" + integrity sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w== "@typescript-eslint/types@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.3.0.tgz#1f0b2d5e140543e2614f06d48fb3ae95193c6ddf" integrity sha512-Cx9TpRvlRjOppGsU6Y6KcJnUDOelja2NNCX6AZwtVHRzaJkdytJWMuYiqi8mS35MRNA3cJSwDzXePfmhU6TANw== -"@typescript-eslint/types@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.1.tgz#23829c73c5fc6f4fcd5346a7780b274f72fee222" - integrity sha512-ave2a18x2Y25q5K05K/U3JQIe2Av4+TNi/2YuzyaXLAsDx6UZkz1boZ7nR/N6Wwae2PpudTZmHFXqu7faXfHmA== +"@typescript-eslint/typescript-estree@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz#20d3b8c8e3cdc8f764bdd5e5b0606dd83da6075b" + integrity sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ== + dependencies: + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/visitor-keys" "4.14.1" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" "@typescript-eslint/typescript-estree@4.3.0": version "4.3.0" @@ -6948,19 +6963,13 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.1.tgz#7307e3f2c9e95df7daa8dc0a34b8c43b7ec0dd32" - integrity sha512-bJ6Fn/6tW2g7WIkCWh3QRlaSU7CdUUK52shx36/J7T5oTQzANvi6raoTsbwGM11+7eBbeem8hCCKbyvAc0X3sQ== +"@typescript-eslint/visitor-keys@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz#e93c2ff27f47ee477a929b970ca89d60a117da91" + integrity sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA== dependencies: - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/visitor-keys" "4.8.1" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" + "@typescript-eslint/types" "4.14.1" + eslint-visitor-keys "^2.0.0" "@typescript-eslint/visitor-keys@4.3.0": version "4.3.0" @@ -6970,14 +6979,6 @@ "@typescript-eslint/types" "4.3.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.1.tgz#794f68ee292d1b2e3aa9690ebedfcb3a8c90e3c3" - integrity sha512-3nrwXFdEYALQh/zW8rFwP4QltqsanCDz4CwWMPiIZmwlk9GlvBeueEIbq05SEq4ganqM0g9nh02xXgv5XI3PeQ== - dependencies: - "@typescript-eslint/types" "4.8.1" - eslint-visitor-keys "^2.0.0" - "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -28571,10 +28572,10 @@ typescript-compare@^0.0.2: dependencies: typescript-logic "^0.0.0" -typescript-fsa-reducers@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/typescript-fsa-reducers/-/typescript-fsa-reducers-1.2.1.tgz#2af1a85f7b88fb0dfb9fa59d5da51a5d7ac6543f" - integrity sha512-Qgn7zEnAU5n3YEWEL5ooEmIWZl9B4QyXD4Y/0DqpUzF0YuTrcsLa7Lht0gFXZ+xqLJXQwo3fEiTfQTDF1fBnMg== +typescript-fsa-reducers@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/typescript-fsa-reducers/-/typescript-fsa-reducers-1.2.2.tgz#1e2c8e8a50ca3e09e0d6fdf2a3b42bdd17de8094" + integrity sha512-IQ2VsIqUvmzVgWNDjxkeOxX97itl/rq+2u82jGsRdzCSFi9OtV4qf1Ec1urvj/eDlPHOaihIL7wMZzLYx9GvFg== typescript-fsa@^3.0.0: version "3.0.0" @@ -28593,10 +28594,10 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@4.1.2, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.5.3, typescript@~3.7.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" - integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== +typescript@4.1.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.5.3, typescript@~3.7.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" + integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== ua-parser-js@^0.7.18: version "0.7.22" From c6cfdee5b02d30263972bf149601323883a8c351 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Tue, 26 Jan 2021 20:23:01 -0700 Subject: [PATCH 12/16] [core.logging] Add ops logs to the KP logging system (#88070) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../src/get_logging_config.ts | 5 +- src/core/README.md | 6 +- .../deprecation/core_deprecations.test.ts | 20 +++ .../config/deprecation/core_deprecations.ts | 12 ++ src/core/server/logging/README.md | 24 +++- src/core/server/logging/ecs.ts | 90 ++++++++++++ src/core/server/logging/index.ts | 7 + .../logging/get_ops_metrics_log.test.ts | 132 ++++++++++++++++++ .../metrics/logging/get_ops_metrics_log.ts | 78 +++++++++++ src/core/server/metrics/logging/index.ts | 9 ++ .../server/metrics/metrics_service.test.ts | 99 ++++++++++++- src/core/server/metrics/metrics_service.ts | 6 +- 12 files changed, 482 insertions(+), 6 deletions(-) create mode 100644 src/core/server/logging/ecs.ts create mode 100644 src/core/server/metrics/logging/get_ops_metrics_log.test.ts create mode 100644 src/core/server/metrics/logging/get_ops_metrics_log.ts create mode 100644 src/core/server/metrics/logging/index.ts diff --git a/packages/kbn-legacy-logging/src/get_logging_config.ts b/packages/kbn-legacy-logging/src/get_logging_config.ts index 5a28ab1271538..280b9f815c78c 100644 --- a/packages/kbn-legacy-logging/src/get_logging_config.ts +++ b/packages/kbn-legacy-logging/src/get_logging_config.ts @@ -28,7 +28,10 @@ export function getLoggingConfiguration(config: LegacyLoggingConfig, opsInterval } else if (config.verbose) { _.defaults(events, { log: '*', - ops: '*', + // To avoid duplicate logs, we explicitly disable this in verbose + // mode as it is already provided by the new logging config under + // the `metrics.ops` context. + ops: '!', request: '*', response: '*', error: '*', diff --git a/src/core/README.md b/src/core/README.md index e195bf30c054c..c73c6aa56bfd0 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -28,8 +28,10 @@ Even though the new validation system provided by the `core` is also based on Jo rules tailored to our needs (e.g. `byteSize`, `duration` etc.). That means that config values that were previously accepted by the "legacy" Kibana may be rejected by the `core` now. -Even though `core` has its own logging system it doesn't output log records directly (e.g. to file or terminal), but instead -forward them to the "legacy" Kibana so that they look the same as the rest of the log records throughout Kibana. +### Logging +`core` has its own [logging system](./server/logging/README.md) and will output log records directly (e.g. to file or terminal) when configured. When no +specific configuration is provided, logs are forwarded to the "legacy" Kibana so that they look the same as the rest of the +log records throughout Kibana. ## Core API Review To provide a stable API for plugin developers, it is important that the Core Public and Server API's are stable and diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index a7c6a63826523..a791362d9166f 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -236,4 +236,24 @@ describe('core deprecations', () => { ).toEqual([`worker-src blob:`]); }); }); + + describe('logging.events.ops', () => { + it('warns when ops events are used', () => { + const { messages } = applyCoreDeprecations({ + logging: { events: { ops: '*' } }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.events.ops\\" has been deprecated and will be removed in 8.0. To access ops data moving forward, please enable debug logs for the \\"metrics.ops\\" context in your logging configuration.", + ] + `); + }); + + it('does not warn when other events are configured', () => { + const { messages } = applyCoreDeprecations({ + logging: { events: { log: '*' } }, + }); + expect(messages).toEqual([]); + }); + }); }); diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 6f6e6c3e0522e..23a3518cd8eb6 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -103,6 +103,17 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = (settings, fromPath, return settings; }; +const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.events.ops')) { + log( + '"logging.events.ops" has been deprecated and will be removed ' + + 'in 8.0. To access ops data moving forward, please enable debug logs for the ' + + '"metrics.ops" context in your logging configuration.' + ); + } + return settings; +}; + export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [ unusedFromRoot('savedObjects.indexCheckTimeout'), unusedFromRoot('server.xsrf.token'), @@ -137,4 +148,5 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unu rewriteBasePathDeprecation, cspRulesDeprecation, mapManifestServiceUrlDeprecation, + opsLoggingEventDeprecation, ]; diff --git a/src/core/server/logging/README.md b/src/core/server/logging/README.md index 8cb704f09ce8c..cc2b6230d2d33 100644 --- a/src/core/server/logging/README.md +++ b/src/core/server/logging/README.md @@ -312,6 +312,9 @@ logging: - context: telemetry level: all appenders: [json-file-appender] + - context: metrics.ops + level: debug + appenders: [console] ``` Here is what we get with the config above: @@ -324,6 +327,7 @@ Here is what we get with the config above: | server | console, file | fatal | | optimize | console | error | | telemetry | json-file-appender | all | +| metrics.ops | console | debug | The `root` logger has a dedicated configuration node since this context is special and should always exist. By @@ -341,7 +345,25 @@ Or disable logging entirely with `off`: ```yaml logging.root.level: off ``` +### Dedicated loggers + +The `metrics.ops` logger is configured with `debug` level and will automatically output sample system and process information at a regular interval. +The metrics that are logged are a subset of the data collected and are formatted in the log message as follows: + +| Ops formatted log property | Location in metrics service | Log units +| :------------------------- | :-------------------------- | :-------------------------- | +| memory | process.memory.heap.used_in_bytes | [depends on the value](http://numeraljs.com/#format), typically MB or GB | +| uptime | process.uptime_in_millis | HH:mm:ss | +| load | os.load | [ "load for the last 1 min" "load for the last 5 min" "load for the last 15 min"] | +| delay | process.event_loop_delay | ms | + +The log interval is the same as the interval at which system and process information is refreshed and is configurable under `ops.interval`: + +```yaml +ops.interval: 5000 +``` +The minimum interval is 100ms and defaults to 5000ms. ## Usage Usage is very straightforward, one should just get a logger for a specific context and use it to log messages with @@ -478,4 +500,4 @@ TBD | meta | separate property `"meta": {"to": "v8"}` | merged in log record `{... "to": "v8"}` | | pid | `pid: 12345` | `pid: 12345` | | type | N/A | `type: log` | -| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` | \ No newline at end of file +| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` | diff --git a/src/core/server/logging/ecs.ts b/src/core/server/logging/ecs.ts new file mode 100644 index 0000000000000..0dbc403fca0b2 --- /dev/null +++ b/src/core/server/logging/ecs.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Typings for some ECS fields which core uses internally. + * These are not a complete set of ECS typings and should not + * be used externally; the only types included here are ones + * currently used in core. + * + * @internal + */ + +export interface EcsOpsMetricsEvent { + /** + * These typings were written as of ECS 1.7.0. + * Don't change this value without checking the rest + * of the types to conform to that ECS version. + * + * https://www.elastic.co/guide/en/ecs/1.7/index.html + */ + ecs: { version: '1.7.0' }; + + // base fields + ['@timestamp']?: string; + labels?: Record; + message?: string; + tags?: string[]; + // other fields + process?: EcsProcessField; + event?: EcsEventField; +} + +interface EcsProcessField { + uptime?: number; +} + +export interface EcsEventField { + kind?: EcsEventKind; + category?: EcsEventCategory[]; + type?: EcsEventType; +} + +export enum EcsEventKind { + ALERT = 'alert', + EVENT = 'event', + METRIC = 'metric', + STATE = 'state', + PIPELINE_ERROR = 'pipeline_error', + SIGNAL = 'signal', +} + +export enum EcsEventCategory { + AUTHENTICATION = 'authentication', + CONFIGURATION = 'configuration', + DATABASE = 'database', + DRIVER = 'driver', + FILE = 'file', + HOST = 'host', + IAM = 'iam', + INTRUSION_DETECTION = 'intrusion_detection', + MALWARE = 'malware', + NETWORK = 'network', + PACKAGE = 'package', + PROCESS = 'process', + WEB = 'web', +} + +export enum EcsEventType { + ACCESS = 'access', + ADMIN = 'admin', + ALLOWED = 'allowed', + CHANGE = 'change', + CONNECTION = 'connection', + CREATION = 'creation', + DELETION = 'deletion', + DENIED = 'denied', + END = 'end', + ERROR = 'error', + GROUP = 'group', + INFO = 'info', + INSTALLATION = 'installation', + PROTOCOL = 'protocol', + START = 'start', + USER = 'user', +} diff --git a/src/core/server/logging/index.ts b/src/core/server/logging/index.ts index f024bea1bf358..18a903af0a9fd 100644 --- a/src/core/server/logging/index.ts +++ b/src/core/server/logging/index.ts @@ -17,6 +17,13 @@ export { LogLevelId, LogLevel, } from '@kbn/logging'; +export { + EcsOpsMetricsEvent, + EcsEventField, + EcsEventKind, + EcsEventCategory, + EcsEventType, +} from './ecs'; export { config, LoggingConfigType, diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.test.ts b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts new file mode 100644 index 0000000000000..820959910e764 --- /dev/null +++ b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { OpsMetrics } from '..'; +import { getEcsOpsMetricsLog } from './get_ops_metrics_log'; + +function createBaseOpsMetrics(): OpsMetrics { + return { + collected_at: new Date('2020-01-01 01:00:00'), + process: { + memory: { + heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 }, + resident_set_size_in_bytes: 1, + }, + event_loop_delay: 1, + pid: 1, + uptime_in_millis: 1, + }, + os: { + platform: 'darwin' as const, + platformRelease: 'test', + load: { '1m': 1, '5m': 1, '15m': 1 }, + memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 }, + uptime_in_millis: 1, + }, + response_times: { avg_in_millis: 1, max_in_millis: 1 }, + requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } }, + concurrent_connections: 1, + }; +} + +function createMockOpsMetrics(testMetrics: Partial): OpsMetrics { + const base = createBaseOpsMetrics(); + return { + ...base, + ...testMetrics, + }; +} +const testMetrics = ({ + process: { + memory: { heap: { used_in_bytes: 100 } }, + uptime_in_millis: 1500, + event_loop_delay: 50, + }, + os: { + load: { + '1m': 10, + '5m': 20, + '15m': 30, + }, + }, +} as unknown) as Partial; + +describe('getEcsOpsMetricsLog', () => { + it('provides correctly formatted message', () => { + const result = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); + expect(result.message).toMatchInlineSnapshot( + `"memory: 100.0B uptime: 0:00:01 load: [10.00,20.00,30.00] delay: 50.000"` + ); + }); + + it('correctly formats process uptime', () => { + const logMeta = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); + expect(logMeta.process!.uptime).toEqual(1); + }); + + it('excludes values from the message if unavailable', () => { + const baseMetrics = createBaseOpsMetrics(); + const missingMetrics = ({ + ...baseMetrics, + process: {}, + os: {}, + } as unknown) as OpsMetrics; + const logMeta = getEcsOpsMetricsLog(missingMetrics); + expect(logMeta.message).toMatchInlineSnapshot(`""`); + }); + + it('specifies correct ECS version', () => { + const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); + expect(logMeta.ecs.version).toBe('1.7.0'); + }); + + it('provides an ECS-compatible response', () => { + const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); + expect(logMeta).toMatchInlineSnapshot(` + Object { + "ecs": Object { + "version": "1.7.0", + }, + "event": Object { + "category": Array [ + "process", + "host", + ], + "kind": "metric", + "type": "info", + }, + "host": Object { + "os": Object { + "load": Object { + "15m": 1, + "1m": 1, + "5m": 1, + }, + }, + }, + "message": "memory: 1.0B load: [1.00,1.00,1.00] delay: 1.000", + "process": Object { + "eventLoopDelay": 1, + "memory": Object { + "heap": Object { + "usedInBytes": 1, + }, + }, + "uptime": 0, + }, + } + `); + }); + + it('logs ECS fields in the log meta', () => { + const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); + expect(logMeta.event!.kind).toBe('metric'); + expect(logMeta.event!.category).toEqual(expect.arrayContaining(['process', 'host'])); + expect(logMeta.event!.type).toBe('info'); + }); +}); diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.ts b/src/core/server/metrics/logging/get_ops_metrics_log.ts new file mode 100644 index 0000000000000..361cac0bc310c --- /dev/null +++ b/src/core/server/metrics/logging/get_ops_metrics_log.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import numeral from '@elastic/numeral'; +import { EcsOpsMetricsEvent, EcsEventKind, EcsEventCategory, EcsEventType } from '../../logging'; +import { OpsMetrics } from '..'; + +const ECS_VERSION = '1.7.0'; +/** + * Converts ops metrics into ECS-compliant `LogMeta` for logging + * + * @internal + */ +export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsOpsMetricsEvent { + const { process, os } = metrics; + const processMemoryUsedInBytes = process?.memory?.heap?.used_in_bytes; + const processMemoryUsedInBytesMsg = processMemoryUsedInBytes + ? `memory: ${numeral(processMemoryUsedInBytes).format('0.0b')} ` + : ''; + + // ECS process.uptime is in seconds: + const uptimeVal = process?.uptime_in_millis + ? Math.floor(process.uptime_in_millis / 1000) + : undefined; + + // HH:mm:ss message format for backward compatibility + const uptimeValMsg = uptimeVal ? `uptime: ${numeral(uptimeVal).format('00:00:00')} ` : ''; + + // Event loop delay is in ms + const eventLoopDelayVal = process?.event_loop_delay; + const eventLoopDelayValMsg = eventLoopDelayVal + ? `delay: ${numeral(process?.event_loop_delay).format('0.000')}` + : ''; + + const loadEntries = { + '1m': os?.load ? os?.load['1m'] : undefined, + '5m': os?.load ? os?.load['5m'] : undefined, + '15m': os?.load ? os?.load['15m'] : undefined, + }; + + const loadVals = [...Object.values(os?.load ?? [])]; + const loadValsMsg = + loadVals.length > 0 + ? `load: [${loadVals.map((val: number) => { + return numeral(val).format('0.00'); + })}] ` + : ''; + + return { + ecs: { version: ECS_VERSION }, + message: `${processMemoryUsedInBytesMsg}${uptimeValMsg}${loadValsMsg}${eventLoopDelayValMsg}`, + event: { + kind: EcsEventKind.METRIC, + category: [EcsEventCategory.PROCESS, EcsEventCategory.HOST], + type: EcsEventType.INFO, + }, + process: { + uptime: uptimeVal, + // @ts-expect-error custom fields not yet part of ECS + memory: { + heap: { + usedInBytes: processMemoryUsedInBytes, + }, + }, + eventLoopDelay: eventLoopDelayVal, + }, + host: { + os: { + load: loadEntries, + }, + }, + }; +} diff --git a/src/core/server/metrics/logging/index.ts b/src/core/server/metrics/logging/index.ts new file mode 100644 index 0000000000000..5b3f9aed56be0 --- /dev/null +++ b/src/core/server/metrics/logging/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +export { getEcsOpsMetricsLog } from './get_ops_metrics_log'; diff --git a/src/core/server/metrics/metrics_service.test.ts b/src/core/server/metrics/metrics_service.test.ts index 5ba29606d8a80..e21bad1ef4be7 100644 --- a/src/core/server/metrics/metrics_service.test.ts +++ b/src/core/server/metrics/metrics_service.test.ts @@ -13,12 +13,15 @@ import { mockOpsCollector } from './metrics_service.test.mocks'; import { MetricsService } from './metrics_service'; import { mockCoreContext } from '../core_context.mock'; import { httpServiceMock } from '../http/http_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { take } from 'rxjs/operators'; const testInterval = 100; const dummyMetrics = { metricA: 'value', metricB: 'otherValue' }; +const logger = loggingSystemMock.create(); + describe('MetricsService', () => { const httpMock = httpServiceMock.createInternalSetupContract(); let metricsService: MetricsService; @@ -29,7 +32,7 @@ describe('MetricsService', () => { const configService = configServiceMock.create({ atPath: { interval: moment.duration(testInterval) }, }); - const coreContext = mockCoreContext.create({ configService }); + const coreContext = mockCoreContext.create({ logger, configService }); metricsService = new MetricsService(coreContext); }); @@ -118,6 +121,100 @@ describe('MetricsService', () => { expect(await nextEmission()).toEqual({ metric: 'first' }); expect(await nextEmission()).toEqual({ metric: 'second' }); }); + + it('logs the metrics at every interval', async () => { + const firstMetrics = { + process: { + memory: { heap: { used_in_bytes: 100 } }, + uptime_in_millis: 1500, + event_loop_delay: 50, + }, + os: { + load: { + '1m': 10, + '5m': 20, + '15m': 30, + }, + }, + }; + const secondMetrics = { + process: { + memory: { heap: { used_in_bytes: 200 } }, + uptime_in_millis: 3000, + event_loop_delay: 100, + }, + os: { + load: { + '1m': 20, + '5m': 30, + '15m': 40, + }, + }, + }; + + const opsLogger = logger.get('metrics', 'ops'); + + mockOpsCollector.collect + .mockResolvedValueOnce(firstMetrics) + .mockResolvedValueOnce(secondMetrics); + await metricsService.setup({ http: httpMock }); + const { getOpsMetrics$ } = await metricsService.start(); + + const nextEmission = async () => { + jest.advanceTimersByTime(testInterval); + const emission = await getOpsMetrics$().pipe(take(1)).toPromise(); + await new Promise((resolve) => process.nextTick(resolve)); + return emission; + }; + + await nextEmission(); + const opsLogs = loggingSystemMock.collect(opsLogger).debug; + expect(opsLogs.length).toEqual(2); + expect(opsLogs[0][1]).not.toEqual(opsLogs[1][1]); + }); + + it('omits metrics from log message if they are missing or malformed', async () => { + const opsLogger = logger.get('metrics', 'ops'); + mockOpsCollector.collect.mockResolvedValueOnce({ secondMetrics: 'metrics' }); + await metricsService.setup({ http: httpMock }); + await metricsService.start(); + expect(loggingSystemMock.collect(opsLogger).debug[0]).toMatchInlineSnapshot(` + Array [ + "", + Object { + "ecs": Object { + "version": "1.7.0", + }, + "event": Object { + "category": Array [ + "process", + "host", + ], + "kind": "metric", + "type": "info", + }, + "host": Object { + "os": Object { + "load": Object { + "15m": undefined, + "1m": undefined, + "5m": undefined, + }, + }, + }, + "process": Object { + "eventLoopDelay": undefined, + "memory": Object { + "heap": Object { + "usedInBytes": undefined, + }, + }, + "uptime": undefined, + }, + }, + ] + `); + }); }); describe('#stop', () => { diff --git a/src/core/server/metrics/metrics_service.ts b/src/core/server/metrics/metrics_service.ts index 24b2b1b67a07a..460035ad2e298 100644 --- a/src/core/server/metrics/metrics_service.ts +++ b/src/core/server/metrics/metrics_service.ts @@ -15,6 +15,7 @@ import { InternalHttpServiceSetup } from '../http'; import { InternalMetricsServiceSetup, InternalMetricsServiceStart, OpsMetrics } from './types'; import { OpsMetricsCollector } from './ops_metrics_collector'; import { opsConfig, OpsConfigType } from './ops_config'; +import { getEcsOpsMetricsLog } from './logging'; interface MetricsServiceSetupDeps { http: InternalHttpServiceSetup; @@ -24,6 +25,7 @@ interface MetricsServiceSetupDeps { export class MetricsService implements CoreService { private readonly logger: Logger; + private readonly opsMetricsLogger: Logger; private metricsCollector?: OpsMetricsCollector; private collectInterval?: NodeJS.Timeout; private metrics$ = new ReplaySubject(1); @@ -31,6 +33,7 @@ export class MetricsService constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger.get('metrics'); + this.opsMetricsLogger = coreContext.logger.get('metrics', 'ops'); } public async setup({ http }: MetricsServiceSetupDeps): Promise { @@ -69,8 +72,9 @@ export class MetricsService } private async refreshMetrics() { - this.logger.debug('Refreshing metrics'); const metrics = await this.metricsCollector!.collect(); + const { message, ...meta } = getEcsOpsMetricsLog(metrics); + this.opsMetricsLogger.debug(message!, meta); this.metricsCollector!.reset(); this.metrics$.next(metrics); } From 3fe2e95e350ce8d13272df633d97aa2eabc62a07 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 26 Jan 2021 22:53:14 -0500 Subject: [PATCH 13/16] Rename conversion function, extract to module scope and add tests. (#89018) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/requests/get_network_events.test.ts | 12 +++++++++++- .../uptime/server/lib/requests/get_network_events.ts | 12 +++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts index 2d590e80ca42d..e1c62f3469518 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts @@ -5,9 +5,19 @@ */ import { getUptimeESMockClient } from './helper'; -import { getNetworkEvents } from './get_network_events'; +import { getNetworkEvents, secondsToMillis } from './get_network_events'; describe('getNetworkEvents', () => { + describe('secondsToMillis conversion', () => { + it('returns -1 for -1 value', () => { + expect(secondsToMillis(-1)).toBe(-1); + }); + + it('returns a value of seconds as milliseconds', () => { + expect(secondsToMillis(10)).toBe(10_000); + }); + }); + let mockHits: any; beforeEach(() => { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts index ec1fffd62350d..6b1bca8f2d7b7 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts @@ -12,6 +12,10 @@ interface GetNetworkEventsParams { stepIndex: string; } +export const secondsToMillis = (seconds: number) => + // -1 is a special case where a value was unavailable + seconds === -1 ? -1 : seconds * 1000; + export const getNetworkEvents: UMElasticsearchQueryFn< GetNetworkEventsParams, { events: NetworkEvent[]; total: number } @@ -35,17 +39,15 @@ export const getNetworkEvents: UMElasticsearchQueryFn< const { body: result } = await uptimeEsClient.search({ body: params }); - const microToMillis = (micro: number): number => (micro === -1 ? -1 : micro * 1000); - return { total: result.hits.total.value, events: result.hits.hits.map((event: any) => { - const requestSentTime = microToMillis(event._source.synthetics.payload.request_sent_time); - const loadEndTime = microToMillis(event._source.synthetics.payload.load_end_time); + const requestSentTime = secondsToMillis(event._source.synthetics.payload.request_sent_time); + const loadEndTime = secondsToMillis(event._source.synthetics.payload.load_end_time); const requestStartTime = event._source.synthetics.payload.response && event._source.synthetics.payload.response.timing - ? microToMillis(event._source.synthetics.payload.response.timing.request_time) + ? secondsToMillis(event._source.synthetics.payload.response.timing.request_time) : undefined; return { From 053628cae862103b8c27bc60656bf45d76dc3f5d Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 27 Jan 2021 09:54:56 +0100 Subject: [PATCH 14/16] [APM] Optimize API test order (#88654) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../fake_mocha_types.d.ts | 2 + .../snapshots/decorate_snapshot_ui.test.ts | 63 ++- .../lib/snapshots/decorate_snapshot_ui.ts | 89 ++-- .../test/apm_api_integration/basic/config.ts | 8 +- .../basic/tests/alerts/chart_preview.ts | 124 ----- .../apm_api_integration/basic/tests/index.ts | 72 --- .../tests/metrics_charts/metrics_charts.ts | 439 ---------------- .../basic/tests/service_maps/service_maps.ts | 32 -- .../basic/tests/services/annotations.ts | 48 -- .../basic/tests/services/throughput.ts | 85 --- .../basic/tests/services/top_services.ts | 259 --------- .../tests/settings/agent_configuration.ts | 489 ----------------- .../anomaly_detection/no_access_user.ts | 43 -- .../settings/anomaly_detection/read_user.ts | 46 -- .../settings/anomaly_detection/write_user.ts | 50 -- .../basic/tests/settings/custom_link.ts | 35 -- .../basic/tests/transactions/breakdown.ts | 123 ----- .../basic/tests/transactions/latency.ts | 102 ---- .../test/apm_api_integration/common/config.ts | 17 +- .../apm_api_integration/common/registry.ts | 160 ++++++ .../test/apm_api_integration/configs/index.ts | 25 + .../tests/alerts/chart_preview.ts | 70 +++ .../tests/correlations/slow_transactions.ts | 88 ++++ .../csm/__snapshots__/page_load_dist.snap | 8 +- .../tests/csm/__snapshots__/page_views.snap | 8 +- .../tests/csm/csm_services.ts | 40 ++ .../{trial => }/tests/csm/has_rum_data.ts | 39 +- .../{trial => }/tests/csm/js_errors.ts | 39 +- .../tests/csm/long_task_metrics.ts | 47 +- .../tests/csm/page_load_dist.ts | 58 ++ .../tests/csm/page_views.ts | 58 ++ .../tests/csm/url_search.ts | 82 +++ .../{trial => }/tests/csm/web_core_vitals.ts | 51 +- .../{basic => }/tests/feature_controls.ts | 5 +- .../test/apm_api_integration/tests/index.ts | 70 +++ .../tests/metrics_charts/metrics_charts.ts | 446 ++++++++++++++++ .../tests/observability_overview/has_data.ts | 37 +- .../observability_overview.ts | 42 +- .../__snapshots__/service_maps.snap | 6 +- .../tests/service_maps/service_maps.ts | 155 +++--- .../service_overview/dependencies/es_utils.ts | 0 .../service_overview/dependencies/index.ts | 44 +- .../tests/service_overview/error_groups.ts | 27 +- .../tests/service_overview/instances.ts | 65 +-- .../services/__snapshots__/throughput.snap | 2 +- .../{basic => }/tests/services/agent_name.ts | 35 +- .../{trial => }/tests/services/annotations.ts | 30 +- .../tests/services/service_details.ts | 27 +- .../tests/services/service_icons.ts | 63 ++- .../tests/services/throughput.ts | 82 +++ .../tests/services/top_services.ts | 364 +++++++++++++ .../tests/services/transaction_types.ts | 27 +- .../tests/settings/agent_configuration.ts | 495 ++++++++++++++++++ .../tests/settings/anomaly_detection/basic.ts | 53 ++ .../anomaly_detection/no_access_user.ts | 44 ++ .../settings/anomaly_detection/read_user.ts | 46 ++ .../settings/anomaly_detection/write_user.ts | 56 ++ .../tests/settings/custom_link.ts | 183 +++++++ .../traces/__snapshots__/top_traces.snap | 2 +- .../{basic => }/tests/traces/top_traces.ts | 36 +- .../transactions/__snapshots__/breakdown.snap | 4 +- .../__snapshots__/error_rate.snap | 2 +- .../transactions/__snapshots__/latency.snap | 46 ++ .../__snapshots__/top_transaction_groups.snap | 2 +- .../__snapshots__/transaction_charts.snap | 0 .../__snapshots__/transactions_charts.snap | 0 .../tests/transactions/breakdown.ts | 118 +++++ .../tests/transactions/distribution.ts | 26 +- .../tests/transactions/error_rate.ts | 43 +- .../tests/transactions/latency.ts | 243 +++++++++ .../tests/transactions/throughput.ts | 53 +- .../transactions/top_transaction_groups.ts | 26 +- .../transactions_groups_overview.ts | 27 +- .../test/apm_api_integration/trial/config.ts | 8 +- .../tests/correlations/slow_transactions.ts | 95 ---- .../trial/tests/csm/csm_services.ts | 47 -- .../trial/tests/csm/page_load_dist.ts | 64 --- .../trial/tests/csm/page_views.ts | 64 --- .../trial/tests/csm/url_search.ts | 89 ---- .../apm_api_integration/trial/tests/index.ts | 50 -- .../trial/tests/services/top_services.ts | 131 ----- .../anomaly_detection/no_access_user.ts | 41 -- .../settings/anomaly_detection/read_user.ts | 43 -- .../settings/anomaly_detection/write_user.ts | 53 -- .../trial/tests/settings/custom_link.ts | 159 ------ .../transactions/__snapshots__/latency.snap | 46 -- .../trial/tests/transactions/latency.ts | 159 ------ 87 files changed, 3447 insertions(+), 3533 deletions(-) delete mode 100644 x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/index.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/services/annotations.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/services/throughput.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/services/top_services.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts create mode 100644 x-pack/test/apm_api_integration/common/registry.ts create mode 100644 x-pack/test/apm_api_integration/configs/index.ts create mode 100644 x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts create mode 100644 x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts rename x-pack/test/apm_api_integration/{trial => }/tests/csm/__snapshots__/page_load_dist.snap (95%) rename x-pack/test/apm_api_integration/{trial => }/tests/csm/__snapshots__/page_views.snap (90%) create mode 100644 x-pack/test/apm_api_integration/tests/csm/csm_services.ts rename x-pack/test/apm_api_integration/{trial => }/tests/csm/has_rum_data.ts (53%) rename x-pack/test/apm_api_integration/{trial => }/tests/csm/js_errors.ts (72%) rename x-pack/test/apm_api_integration/{trial => }/tests/csm/long_task_metrics.ts (50%) create mode 100644 x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts create mode 100644 x-pack/test/apm_api_integration/tests/csm/page_views.ts create mode 100644 x-pack/test/apm_api_integration/tests/csm/url_search.ts rename x-pack/test/apm_api_integration/{trial => }/tests/csm/web_core_vitals.ts (55%) rename x-pack/test/apm_api_integration/{basic => }/tests/feature_controls.ts (98%) create mode 100644 x-pack/test/apm_api_integration/tests/index.ts create mode 100644 x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts rename x-pack/test/apm_api_integration/{basic => }/tests/observability_overview/has_data.ts (67%) rename x-pack/test/apm_api_integration/{basic => }/tests/observability_overview/observability_overview.ts (69%) rename x-pack/test/apm_api_integration/{trial => }/tests/service_maps/__snapshots__/service_maps.snap (99%) rename x-pack/test/apm_api_integration/{trial => }/tests/service_maps/service_maps.ts (58%) rename x-pack/test/apm_api_integration/{basic => }/tests/service_overview/dependencies/es_utils.ts (100%) rename x-pack/test/apm_api_integration/{basic => }/tests/service_overview/dependencies/index.ts (91%) rename x-pack/test/apm_api_integration/{basic => }/tests/service_overview/error_groups.ts (94%) rename x-pack/test/apm_api_integration/{basic => }/tests/service_overview/instances.ts (83%) rename x-pack/test/apm_api_integration/{basic => }/tests/services/__snapshots__/throughput.snap (96%) rename x-pack/test/apm_api_integration/{basic => }/tests/services/agent_name.ts (55%) rename x-pack/test/apm_api_integration/{trial => }/tests/services/annotations.ts (92%) rename x-pack/test/apm_api_integration/{basic => }/tests/services/service_details.ts (87%) rename x-pack/test/apm_api_integration/{basic => }/tests/services/service_icons.ts (53%) create mode 100644 x-pack/test/apm_api_integration/tests/services/throughput.ts create mode 100644 x-pack/test/apm_api_integration/tests/services/top_services.ts rename x-pack/test/apm_api_integration/{basic => }/tests/services/transaction_types.ts (75%) create mode 100644 x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/custom_link.ts rename x-pack/test/apm_api_integration/{basic => }/tests/traces/__snapshots__/top_traces.snap (99%) rename x-pack/test/apm_api_integration/{basic => }/tests/traces/top_traces.ts (81%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/__snapshots__/breakdown.snap (98%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/__snapshots__/error_rate.snap (95%) create mode 100644 x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/__snapshots__/top_transaction_groups.snap (96%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/__snapshots__/transaction_charts.snap (100%) rename x-pack/test/apm_api_integration/{trial => }/tests/transactions/__snapshots__/transactions_charts.snap (100%) create mode 100644 x-pack/test/apm_api_integration/tests/transactions/breakdown.ts rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/distribution.ts (85%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/error_rate.ts (71%) create mode 100644 x-pack/test/apm_api_integration/tests/transactions/latency.ts rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/throughput.ts (54%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/top_transaction_groups.ts (80%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/transactions_groups_overview.ts (94%) delete mode 100644 x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/index.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/services/top_services.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap delete mode 100644 x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts diff --git a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts index 6efc8774b0139..e78cbbb3aed02 100644 --- a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts +++ b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts @@ -20,6 +20,8 @@ export interface Suite { title: string; file?: string; parent?: Suite; + eachTest: (cb: (test: Test) => void) => void; + root: boolean; } export interface Test { diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts index 2a238cdeb5385..a8b0252e6f51c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts @@ -6,21 +6,44 @@ * Public License, v 1. */ -import { Test } from '../../fake_mocha_types'; +import { Suite, Test } from '../../fake_mocha_types'; import { Lifecycle } from '../lifecycle'; import { decorateSnapshotUi, expectSnapshot } from './decorate_snapshot_ui'; import path from 'path'; import fs from 'fs'; +const createMockSuite = ({ tests, root = true }: { tests: Test[]; root?: boolean }) => { + const suite = { + tests, + root, + eachTest: (cb: (test: Test) => void) => { + suite.tests.forEach((test) => cb(test)); + }, + } as Suite; + + return suite; +}; + const createMockTest = ({ title = 'Test', passed = true, -}: { title?: string; passed?: boolean } = {}) => { - return { + filename = __filename, + parent, +}: { title?: string; passed?: boolean; filename?: string; parent?: Suite } = {}) => { + const test = ({ + file: filename, fullTitle: () => title, isPassed: () => passed, - parent: {}, - } as Test; + } as unknown) as Test; + + if (parent) { + parent.tests.push(test); + test.parent = parent; + } else { + test.parent = createMockSuite({ tests: [test] }); + } + + return test; }; describe('decorateSnapshotUi', () => { @@ -211,7 +234,7 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('bar').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger({}); + const afterTestSuite = lifecycle.afterTestSuite.trigger(test.parent); await expect(afterTestSuite).resolves.toBe(undefined); }); @@ -225,7 +248,7 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('foo').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger({}); + const afterTestSuite = lifecycle.afterTestSuite.trigger(test.parent); await expect(afterTestSuite).rejects.toMatchInlineSnapshot(` [Error: 1 obsolete snapshot(s) found: @@ -234,6 +257,32 @@ exports[\`Test2 1\`] = \`"bar"\`; Run tests again with \`--updateSnapshots\` to remove them.] `); }); + + it('does not throw on unused when some tests are skipped', async () => { + const root = createMockSuite({ tests: [] }); + + const test = createMockTest({ + title: 'Test', + parent: root, + passed: true, + }); + + createMockTest({ + title: 'Test2', + parent: root, + passed: false, + }); + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + + const afterTestSuite = lifecycle.afterTestSuite.trigger(root); + + await expect(afterTestSuite).resolves.toBeUndefined(); + }); }); }); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 2111f1a6e5e90..4a52299ecf6d1 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -16,9 +16,8 @@ import path from 'path'; import prettier from 'prettier'; import babelTraverse from '@babel/traverse'; import { once } from 'lodash'; -import callsites from 'callsites'; import { Lifecycle } from '../lifecycle'; -import { Test } from '../../fake_mocha_types'; +import { Suite, Test } from '../../fake_mocha_types'; type ISnapshotState = InstanceType; @@ -33,12 +32,12 @@ const globalState: { updateSnapshot: SnapshotUpdateState; registered: boolean; currentTest: Test | null; - snapshots: Array<{ tests: Test[]; file: string; snapshotState: ISnapshotState }>; + snapshotStates: Record; } = { updateSnapshot: 'none', registered: false, currentTest: null, - snapshots: [], + snapshotStates: {}, }; const modifyStackTracePrepareOnce = once(() => { @@ -73,7 +72,7 @@ export function decorateSnapshotUi({ isCi: boolean; }) { globalState.registered = true; - globalState.snapshots.length = 0; + globalState.snapshotStates = {}; globalState.currentTest = null; if (isCi) { @@ -102,32 +101,36 @@ export function decorateSnapshotUi({ globalState.currentTest = test; }); - lifecycle.afterTestSuite.add(function (testSuite) { + lifecycle.afterTestSuite.add(function (testSuite: Suite) { // save snapshot & check unused after top-level test suite completes - if (testSuite.parent?.parent) { + if (!testSuite.root) { return; } - const unused: string[] = []; + testSuite.eachTest((test) => { + const file = test.file; - globalState.snapshots.forEach((snapshot) => { - const { tests, snapshotState } = snapshot; - tests.forEach((test) => { - const title = test.fullTitle(); - // If test is failed or skipped, mark snapshots as used. Otherwise, - // running a test in isolation will generate false positives. - if (!test.isPassed()) { - snapshotState.markSnapshotsAsCheckedForTest(title); - } - }); + if (!file) { + return; + } + + const snapshotState = globalState.snapshotStates[file]; + + if (snapshotState && !test.isPassed()) { + snapshotState.markSnapshotsAsCheckedForTest(test.fullTitle()); + } + }); + + const unused: string[] = []; - if (globalState.updateSnapshot !== 'all') { - unused.push(...snapshotState.getUncheckedKeys()); - } else { - snapshotState.removeUncheckedKeys(); + Object.values(globalState.snapshotStates).forEach((state) => { + if (globalState.updateSnapshot === 'all') { + state.removeUncheckedKeys(); } - snapshotState.save(); + unused.push(...state.getUncheckedKeys()); + + state.save(); }); if (unused.length) { @@ -138,7 +141,7 @@ export function decorateSnapshotUi({ ); } - globalState.snapshots.length = 0; + globalState.snapshotStates = {}; }); } @@ -161,43 +164,29 @@ function getSnapshotState(file: string, updateSnapshot: SnapshotUpdateState) { export function expectSnapshot(received: any) { if (!globalState.registered) { - throw new Error( - 'Mocha hooks were not registered before expectSnapshot was used. Call `registerMochaHooksForSnapshots` in your top-level describe().' - ); - } - - if (!globalState.currentTest) { - throw new Error('expectSnapshot can only be called inside of an it()'); + throw new Error('expectSnapshot UI was not initialized before calling expectSnapshot()'); } - const [, fileOfTest] = callsites().map((site) => site.getFileName()); + const test = globalState.currentTest; - if (!fileOfTest) { - throw new Error("Couldn't infer a filename for the current test"); + if (!test) { + throw new Error('expectSnapshot can only be called inside of an it()'); } - let snapshot = globalState.snapshots.find(({ file }) => file === fileOfTest); - - if (!snapshot) { - snapshot = { - file: fileOfTest, - tests: [], - snapshotState: getSnapshotState(fileOfTest, globalState.updateSnapshot), - }; - globalState.snapshots.unshift(snapshot!); + if (!test.file) { + throw new Error('File for test not found'); } - if (!snapshot) { - throw new Error('Snapshot is undefined'); - } + let snapshotState = globalState.snapshotStates[test.file]; - if (!snapshot.tests.includes(globalState.currentTest)) { - snapshot.tests.push(globalState.currentTest); + if (!snapshotState) { + snapshotState = getSnapshotState(test.file, globalState.updateSnapshot); + globalState.snapshotStates[test.file] = snapshotState; } const context: SnapshotContext = { - snapshotState: snapshot.snapshotState, - currentTestName: globalState.currentTest.fullTitle(), + snapshotState, + currentTestName: test.fullTitle(), }; return { diff --git a/x-pack/test/apm_api_integration/basic/config.ts b/x-pack/test/apm_api_integration/basic/config.ts index 03b8b21bf3232..f3e6ec29f3df2 100644 --- a/x-pack/test/apm_api_integration/basic/config.ts +++ b/x-pack/test/apm_api_integration/basic/config.ts @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createTestConfig } from '../common/config'; +import { configs } from '../configs'; -export default createTestConfig({ - license: 'basic', - name: 'X-Pack APM API integration tests (basic)', - testFiles: [require.resolve('./tests')], -}); +export default configs.basic; diff --git a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts deleted file mode 100644 index 46c0dbeb8940f..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts +++ /dev/null @@ -1,124 +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 { format } from 'url'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const archiveName = 'apm_8.0.0'; - const { end } = archives[archiveName]; - const start = new Date(Date.parse(end) - 600000).toISOString(); - - describe('Alerting chart previews', () => { - describe('GET /api/apm/alerts/chart_preview/transaction_error_rate', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', - query: { - start, - end, - transactionType: 'request', - serviceName: 'opbeans-java', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - - describe('GET /api/apm/alerts/chart_preview/transaction_error_count', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_error_count', - query: { - start, - end, - serviceName: 'opbeans-java', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - - describe('GET /api/apm/alerts/chart_preview/transaction_duration', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_duration', - query: { - start, - end, - serviceName: 'opbeans-java', - transactionType: 'request', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts deleted file mode 100644 index 4e66c6e6f76c3..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ /dev/null @@ -1,72 +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 { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('APM specs (basic)', function () { - this.tags('ciGroup1'); - - loadTestFile(require.resolve('./feature_controls')); - - describe('Alerts', function () { - loadTestFile(require.resolve('./alerts/chart_preview')); - }); - - describe('Service Maps', function () { - loadTestFile(require.resolve('./service_maps/service_maps')); - }); - - describe('Services', function () { - loadTestFile(require.resolve('./services/agent_name')); - loadTestFile(require.resolve('./services/annotations')); - loadTestFile(require.resolve('./services/throughput')); - loadTestFile(require.resolve('./services/top_services')); - loadTestFile(require.resolve('./services/transaction_types')); - loadTestFile(require.resolve('./services/service_details')); - loadTestFile(require.resolve('./services/service_icons')); - }); - - describe('Service overview', function () { - loadTestFile(require.resolve('./service_overview/error_groups')); - loadTestFile(require.resolve('./service_overview/dependencies')); - loadTestFile(require.resolve('./service_overview/instances')); - }); - - describe('Settings', function () { - loadTestFile(require.resolve('./settings/custom_link')); - loadTestFile(require.resolve('./settings/agent_configuration')); - - describe('Anomaly detection', function () { - loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); - }); - }); - - describe('Traces', function () { - loadTestFile(require.resolve('./traces/top_traces')); - }); - - describe('Transactions', function () { - loadTestFile(require.resolve('./transactions/top_transaction_groups')); - loadTestFile(require.resolve('./transactions/latency')); - loadTestFile(require.resolve('./transactions/throughput')); - loadTestFile(require.resolve('./transactions/error_rate')); - loadTestFile(require.resolve('./transactions/breakdown')); - loadTestFile(require.resolve('./transactions/distribution')); - loadTestFile(require.resolve('./transactions/transactions_groups_overview')); - }); - - describe('Observability overview', function () { - loadTestFile(require.resolve('./observability_overview/has_data')); - loadTestFile(require.resolve('./observability_overview/observability_overview')); - }); - - describe('Metrics', function () { - loadTestFile(require.resolve('./metrics_charts/metrics_charts')); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts deleted file mode 100644 index d52aa2727d651..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts +++ /dev/null @@ -1,439 +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 { first } from 'lodash'; -import { MetricsChartsByAgentAPIResponse } from '../../../../../plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent'; -import { GenericMetricsChart } from '../../../../../plugins/apm/server/lib/metrics/transform_metrics_chart'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -interface ChartResponse { - body: MetricsChartsByAgentAPIResponse; - status: number; -} - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('when data is loaded', () => { - before(() => esArchiver.load('metrics_8.0.0')); - after(() => esArchiver.unload('metrics_8.0.0')); - - describe('for opbeans-node', () => { - const start = encodeURIComponent('2020-09-08T14:50:00.000Z'); - const end = encodeURIComponent('2020-09-08T14:55:00.000Z'); - const uiFilters = encodeURIComponent(JSON.stringify({})); - const agentName = 'nodejs'; - - describe('returns metrics data', () => { - let chartsResponse: ChartResponse; - before(async () => { - chartsResponse = await supertest.get( - `/api/apm/services/opbeans-node/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - }); - it('contains CPU usage and System memory usage chart data', async () => { - expect(chartsResponse.status).to.be(200); - expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` - Array [ - "CPU usage", - "System memory usage", - ] - `); - }); - - describe('CPU usage', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find(({ key }) => key === 'cpu_usage_chart'); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "System max", - "System average", - "Process max", - "Process average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.714, - 0.3877, - 0.75, - 0.2543, - ] - `); - }); - }); - - describe("System memory usage (using 'system.memory' fields to calculate the memory usage)", () => { - let systemMemoryUsageChart: GenericMetricsChart | undefined; - before(() => { - systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - }); - - it('has correct series', () => { - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.722093920925555, - 0.718173546796348, - ] - `); - }); - }); - }); - }); - - describe('for opbeans-java', () => { - const uiFilters = encodeURIComponent(JSON.stringify({})); - const agentName = 'java'; - - describe('returns metrics data', () => { - const start = encodeURIComponent('2020-09-08T14:55:30.000Z'); - const end = encodeURIComponent('2020-09-08T15:00:00.000Z'); - - let chartsResponse: ChartResponse; - before(async () => { - chartsResponse = await supertest.get( - `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - }); - - it('has correct chart data', async () => { - expect(chartsResponse.status).to.be(200); - expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` - Array [ - "CPU usage", - "System memory usage", - "Heap Memory", - "Non-Heap Memory", - "Thread Count", - "Garbage collection per minute", - "Garbage collection time spent per minute", - ] - `); - }); - - describe('CPU usage', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find(({ key }) => key === 'cpu_usage_chart'); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "System max", - "System average", - "Process max", - "Process average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.203, - 0.178777777777778, - 0.01, - 0.009, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.193, - 0.193, - 0.009, - 0.009, - ] - `); - }); - }); - - describe("System memory usage (using 'system.process.cgroup' fields to calculate the memory usage)", () => { - let systemMemoryUsageChart: GenericMetricsChart | undefined; - before(() => { - systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - }); - - it('has correct series', () => { - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.707924703557837, - 0.705395980841182, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.707924703557837, - 0.707924703557837, - ] - `); - }); - }); - - describe('Heap Memory', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'heap_memory_area_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. used", - "Avg. committed", - "Avg. limit", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 222501617.777778, - 374341632, - 1560281088, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 211472896, - 374341632, - 1560281088, - ] - `); - }); - }); - - describe('Non-Heap Memory', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'non_heap_memory_area_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. used", - "Avg. committed", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 138573397.333333, - 147677639.111111, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 138162752, - 147386368, - ] - `); - }); - }); - - describe('Thread Count', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'thread_count_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. count", - "Max count", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 44.4444444444444, - 45, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 44, - 44, - ] - `); - }); - }); - - describe('Garbage collection per minute', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'gc_rate_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "G1 Old Generation", - "G1 Young Generation", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0, - 15, - ] - `); - }); - }); - - describe('Garbage collection time spent per minute', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'gc_time_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "G1 Old Generation", - "G1 Young Generation", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0, - 187.5, - ] - `); - }); - }); - }); - - // 9223372036854771712 = memory limit for a c-group when no memory limit is specified - it('calculates system memory usage using system total field when cgroup limit is equal to 9223372036854771712', async () => { - const start = encodeURIComponent('2020-09-08T15:00:30.000Z'); - const end = encodeURIComponent('2020-09-08T15:05:00.000Z'); - - const chartsResponse: ChartResponse = await supertest.get( - `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - - const systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.114523896426499, - 0.114002376090415, - ] - `); - - const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.11383724014064, - 0.11383724014064, - ] - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts deleted file mode 100644 index f44b1561f2a5a..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts +++ /dev/null @@ -1,32 +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 archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function serviceMapsApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - - describe('Service Maps', () => { - it('is only be available to users with Platinum license (or higher)', async () => { - const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - - expect(response.status).to.be(403); - - expectSnapshot(response.body.message).toMatchInline( - `"In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data."` - ); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts b/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts deleted file mode 100644 index 3136dcef2e985..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts +++ /dev/null @@ -1,48 +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 { JsonObject } from 'src/plugins/kibana_utils/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function annotationApiTests({ getService }: FtrProviderContext) { - const supertestWrite = getService('supertestAsApmAnnotationsWriteUser'); - - function request({ method, url, data }: { method: string; url: string; data?: JsonObject }) { - switch (method.toLowerCase()) { - case 'post': - return supertestWrite.post(url).send(data).set('kbn-xsrf', 'foo'); - - default: - throw new Error(`Unsupported method ${method}`); - } - } - - describe('APM annotations with a basic license', () => { - describe('when creating an annotation', () => { - it('fails with a 403 forbidden', async () => { - const response = await request({ - url: '/api/apm/services/opbeans-java/annotation', - method: 'POST', - data: { - '@timestamp': new Date().toISOString(), - message: 'New deployment', - tags: ['foo'], - service: { - version: '1.1', - environment: 'production', - }, - }, - }); - - expect(response.status).to.be(403); - expect(response.body.message).to.be( - 'Annotations require at least a gold license or a trial license.' - ); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts b/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts deleted file mode 100644 index 07a1442d751b4..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts +++ /dev/null @@ -1,85 +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 qs from 'querystring'; -import { first, last } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - describe('Throughput', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/throughput?${qs.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - transactionType: 'request', - })}` - ); - expect(response.status).to.be(200); - expect(response.body.throughput.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('returns the service throughput', () => { - let throughputResponse: { - throughput: Array<{ x: number; y: number | null }>; - }; - before(async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/throughput?${qs.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - transactionType: 'request', - })}` - ); - throughputResponse = response.body; - }); - - it('returns some data', () => { - expect(throughputResponse.throughput.length).to.be.greaterThan(0); - - const nonNullDataPoints = throughputResponse.throughput.filter(({ y }) => y !== null); - - expect(nonNullDataPoints.length).to.be.greaterThan(0); - }); - - it('has the correct start date', () => { - expectSnapshot( - new Date(first(throughputResponse.throughput)?.x ?? NaN).toISOString() - ).toMatchInline(`"2020-12-08T13:57:30.000Z"`); - }); - - it('has the correct end date', () => { - expectSnapshot( - new Date(last(throughputResponse.throughput)?.x ?? NaN).toISOString() - ).toMatchInline(`"2020-12-08T14:27:30.000Z"`); - }); - - it('has the correct number of buckets', () => { - expectSnapshot(throughputResponse.throughput.length).toMatchInline(`61`); - }); - - it('has the correct throughput', () => { - expectSnapshot(throughputResponse.throughput).toMatch(); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts deleted file mode 100644 index 98bfe84cf56ee..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts +++ /dev/null @@ -1,259 +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 { isEmpty, pick, sortBy } from 'lodash'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('APM Services Overview', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - - expect(response.status).to.be(200); - expect(response.body.hasHistoricalData).to.be(false); - expect(response.body.hasLegacyData).to.be(false); - expect(response.body.items.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('and fetching a list of services', () => { - let response: { - status: number; - body: APIReturnType<'GET /api/apm/services'>; - }; - - let sortedItems: typeof response.body.items; - - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - sortedItems = sortBy(response.body.items, 'serviceName'); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('returns hasHistoricalData: true', () => { - expect(response.body.hasHistoricalData).to.be(true); - }); - - it('returns hasLegacyData: false', () => { - expect(response.body.hasLegacyData).to.be(false); - }); - - it('returns the correct service names', () => { - expectSnapshot(sortedItems.map((item) => item.serviceName)).toMatchInline(` - Array [ - "kibana", - "kibana-frontend", - "opbeans-dotnet", - "opbeans-go", - "opbeans-java", - "opbeans-node", - "opbeans-python", - "opbeans-ruby", - "opbeans-rum", - ] - `); - }); - - it('returns the correct metrics averages', () => { - expectSnapshot( - sortedItems.map((item) => - pick( - item, - 'transactionErrorRate.value', - 'avgResponseTime.value', - 'transactionsPerMinute.value' - ) - ) - ).toMatchInline(` - Array [ - Object { - "avgResponseTime": Object { - "value": 420419.34550767, - }, - "transactionErrorRate": Object { - "value": 0, - }, - "transactionsPerMinute": Object { - "value": 45.6333333333333, - }, - }, - Object { - "avgResponseTime": Object { - "value": 2382833.33333333, - }, - "transactionErrorRate": Object { - "value": null, - }, - "transactionsPerMinute": Object { - "value": 0.2, - }, - }, - Object { - "avgResponseTime": Object { - "value": 631521.83908046, - }, - "transactionErrorRate": Object { - "value": 0.0229885057471264, - }, - "transactionsPerMinute": Object { - "value": 2.9, - }, - }, - Object { - "avgResponseTime": Object { - "value": 27946.1484375, - }, - "transactionErrorRate": Object { - "value": 0.015625, - }, - "transactionsPerMinute": Object { - "value": 4.26666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 237339.813333333, - }, - "transactionErrorRate": Object { - "value": 0.16, - }, - "transactionsPerMinute": Object { - "value": 2.5, - }, - }, - Object { - "avgResponseTime": Object { - "value": 24920.1052631579, - }, - "transactionErrorRate": Object { - "value": 0.0210526315789474, - }, - "transactionsPerMinute": Object { - "value": 3.16666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 29542.6607142857, - }, - "transactionErrorRate": Object { - "value": 0.0357142857142857, - }, - "transactionsPerMinute": Object { - "value": 1.86666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 70518.9328358209, - }, - "transactionErrorRate": Object { - "value": 0.0373134328358209, - }, - "transactionsPerMinute": Object { - "value": 4.46666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 2319812.5, - }, - "transactionErrorRate": Object { - "value": null, - }, - "transactionsPerMinute": Object { - "value": 0.533333333333333, - }, - }, - ] - `); - }); - - it('returns environments', () => { - expectSnapshot(sortedItems.map((item) => item.environments ?? [])).toMatchInline(` - Array [ - Array [ - "production", - ], - Array [ - "production", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - Array [], - Array [], - Array [ - "testing", - ], - ] - `); - }); - - it(`RUM services don't report any transaction error rates`, () => { - // RUM transactions don't have event.outcome set, - // so they should not have an error rate - - const rumServices = sortedItems.filter((item) => item.agentName === 'rum-js'); - - expect(rumServices.length).to.be.greaterThan(0); - - expect(rumServices.every((item) => isEmpty(item.transactionErrorRate?.value))); - }); - - it('non-RUM services all report transaction error rates', () => { - const nonRumServices = sortedItems.filter((item) => item.agentName !== 'rum-js'); - - expect( - nonRumServices.every((item) => { - return ( - typeof item.transactionErrorRate?.value === 'number' && - item.transactionErrorRate.timeseries.length > 0 - ); - }) - ).to.be(true); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts deleted file mode 100644 index 1817c7c4511fa..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts +++ /dev/null @@ -1,489 +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 { omit, orderBy } from 'lodash'; -import { AgentConfigurationIntake } from '../../../../../plugins/apm/common/agent_configuration/configuration_types'; -import { AgentConfigSearchParams } from '../../../../../plugins/apm/server/routes/settings/agent_configuration'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function agentConfigurationTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertestAsApmReadUser'); - const supertestWrite = getService('supertestAsApmWriteUser'); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - function getServices() { - return supertestRead - .get(`/api/apm/settings/agent-configuration/services`) - .set('kbn-xsrf', 'foo'); - } - - function getEnvironments(serviceName: string) { - return supertestRead - .get(`/api/apm/settings/agent-configuration/environments?serviceName=${serviceName}`) - .set('kbn-xsrf', 'foo'); - } - - function getAgentName(serviceName: string) { - return supertestRead - .get(`/api/apm/settings/agent-configuration/agent_name?serviceName=${serviceName}`) - .set('kbn-xsrf', 'foo'); - } - - function searchConfigurations(configuration: AgentConfigSearchParams) { - return supertestRead - .post(`/api/apm/settings/agent-configuration/search`) - .send(configuration) - .set('kbn-xsrf', 'foo'); - } - - function getAllConfigurations() { - return supertestRead.get(`/api/apm/settings/agent-configuration`).set('kbn-xsrf', 'foo'); - } - - async function createConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { - log.debug('creating configuration', config.service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .put(`/api/apm/settings/agent-configuration`) - .send(config) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { - log.debug('updating configuration', config.service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .put(`/api/apm/settings/agent-configuration?overwrite=true`) - .send(config) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function deleteConfiguration( - { service }: AgentConfigurationIntake, - { user = 'write' } = {} - ) { - log.debug('deleting configuration', service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .delete(`/api/apm/settings/agent-configuration`) - .send({ service }) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - function throwOnError(res: any) { - const { statusCode, req, body } = res; - if (statusCode !== 200) { - const e = new Error(` - Endpoint: ${req.method} ${req.path} - Service: ${JSON.stringify(res.request._data.service)} - Status code: ${statusCode} - Response: ${body.message}`); - - // @ts-ignore - e.res = res; - - throw e; - } - } - - describe('agent configuration', () => { - describe('when no data is loaded', () => { - it('handles the empty state for services', async () => { - const { body } = await getServices(); - expect(body).to.eql(['ALL_OPTION_VALUE']); - }); - - it('handles the empty state for environments', async () => { - const { body } = await getEnvironments('myservice'); - expect(body).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); - }); - - it('handles the empty state for agent names', async () => { - const { body } = await getAgentName('myservice'); - expect(body).to.eql({}); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns all services', async () => { - const { body } = await getServices(); - expectSnapshot(body).toMatchInline(` - Array [ - "ALL_OPTION_VALUE", - "kibana", - "kibana-frontend", - "opbeans-dotnet", - "opbeans-go", - "opbeans-java", - "opbeans-node", - "opbeans-python", - "opbeans-ruby", - "opbeans-rum", - ] - `); - }); - - it('returns the environments, all unconfigured', async () => { - const { body } = await getEnvironments('opbeans-node'); - - expect(body.map((item: { name: string }) => item.name)).to.contain('ALL_OPTION_VALUE'); - - expect( - body.every((item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false) - ).to.be(true); - - expectSnapshot(body).toMatchInline(` - Array [ - Object { - "alreadyConfigured": false, - "name": "ALL_OPTION_VALUE", - }, - Object { - "alreadyConfigured": false, - "name": "testing", - }, - ] - `); - }); - - it('returns the agent names', async () => { - const { body } = await getAgentName('opbeans-node'); - expect(body).to.eql({ agentName: 'nodejs' }); - }); - }); - - describe('as a read-only user', () => { - const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; - it('throws when attempting to create config', async () => { - try { - await createConfiguration(newConfig, { user: 'read' }); - - // ensure that `createConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - - describe('when a configuration already exists', () => { - before(async () => createConfiguration(newConfig)); - after(async () => deleteConfiguration(newConfig)); - - it('throws when attempting to update config', async () => { - try { - await updateConfiguration(newConfig, { user: 'read' }); - - // ensure that `updateConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - - it('throws when attempting to delete config', async () => { - try { - await deleteConfiguration(newConfig, { user: 'read' }); - - // ensure that `deleteConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - }); - }); - - describe('when creating one configuration', () => { - const newConfig = { - service: {}, - settings: { transaction_sample_rate: '0.55' }, - }; - - const searchParams = { - service: { name: 'myservice', environment: 'development' }, - etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39', - }; - - it('can create and delete config', async () => { - // assert that config does not exist - const res1 = await searchConfigurations(searchParams); - expect(res1.status).to.equal(404); - - // assert that config was created - await createConfiguration(newConfig); - const res2 = await searchConfigurations(searchParams); - expect(res2.status).to.equal(200); - - // assert that config was deleted - await deleteConfiguration(newConfig); - const res3 = await searchConfigurations(searchParams); - expect(res3.status).to.equal(404); - }); - - describe('when a configuration exists', () => { - before(async () => createConfiguration(newConfig)); - after(async () => deleteConfiguration(newConfig)); - - it('can find the config', async () => { - const { status, body } = await searchConfigurations(searchParams); - expect(status).to.equal(200); - expect(body._source.service).to.eql({}); - expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); - }); - - it('can list the config', async () => { - const { status, body } = await getAllConfigurations(); - expect(status).to.equal(200); - expect(omitTimestamp(body)).to.eql([ - { - service: {}, - settings: { transaction_sample_rate: '0.55' }, - applied_by_agent: false, - etag: 'eb88a8997666cc4b33745ef355a1bbd7c4782f2d', - }, - ]); - }); - - it('can update the config', async () => { - await updateConfiguration({ service: {}, settings: { transaction_sample_rate: '0.85' } }); - const { status, body } = await searchConfigurations(searchParams); - expect(status).to.equal(200); - expect(body._source.service).to.eql({}); - expect(body._source.settings).to.eql({ transaction_sample_rate: '0.85' }); - }); - }); - }); - - describe('when creating multiple configurations', () => { - const configs = [ - { - service: {}, - settings: { transaction_sample_rate: '0.1' }, - }, - { - service: { name: 'my_service' }, - settings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'development' }, - settings: { transaction_sample_rate: '0.3' }, - }, - { - service: { environment: 'production' }, - settings: { transaction_sample_rate: '0.4' }, - }, - { - service: { environment: 'development' }, - settings: { transaction_sample_rate: '0.5' }, - }, - ]; - - before(async () => { - await Promise.all(configs.map((config) => createConfiguration(config))); - }); - - after(async () => { - await Promise.all(configs.map((config) => deleteConfiguration(config))); - }); - - const agentsRequests = [ - { - service: { name: 'non_existing_service', environment: 'non_existing_env' }, - expectedSettings: { transaction_sample_rate: '0.1' }, - }, - { - service: { name: 'my_service', environment: 'non_existing_env' }, - expectedSettings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'production' }, - expectedSettings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'development' }, - expectedSettings: { transaction_sample_rate: '0.3' }, - }, - { - service: { name: 'non_existing_service', environment: 'production' }, - expectedSettings: { transaction_sample_rate: '0.4' }, - }, - { - service: { name: 'non_existing_service', environment: 'development' }, - expectedSettings: { transaction_sample_rate: '0.5' }, - }, - ]; - - it('can list all configs', async () => { - const { status, body } = await getAllConfigurations(); - expect(status).to.equal(200); - expect(orderBy(omitTimestamp(body), ['settings.transaction_sample_rate'])).to.eql([ - { - service: {}, - settings: { transaction_sample_rate: '0.1' }, - applied_by_agent: false, - etag: '0758cb18817de60cca29e07480d472694239c4c3', - }, - { - service: { name: 'my_service' }, - settings: { transaction_sample_rate: '0.2' }, - applied_by_agent: false, - etag: 'e04737637056fdf1763bf0ef0d3fcb86e89ae5fc', - }, - { - service: { name: 'my_service', environment: 'development' }, - settings: { transaction_sample_rate: '0.3' }, - applied_by_agent: false, - etag: 'af4dac62621b6762e6281481d1f7523af1124120', - }, - { - service: { environment: 'production' }, - settings: { transaction_sample_rate: '0.4' }, - applied_by_agent: false, - etag: '8d1bf8e6b778b60af351117e2cf53fb1ee570068', - }, - { - service: { environment: 'development' }, - settings: { transaction_sample_rate: '0.5' }, - applied_by_agent: false, - etag: '4ce40da57e3c71daca704121c784b911ec05ae81', - }, - ]); - }); - - for (const agentRequest of agentsRequests) { - it(`${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { - const { status, body } = await searchConfigurations({ - service: agentRequest.service, - etag: 'abc', - }); - - expect(status).to.equal(200); - expect(body._source.settings).to.eql(agentRequest.expectedSettings); - }); - } - }); - - describe('when an agent retrieves a configuration', () => { - const config = { - service: { name: 'myservice', environment: 'development' }, - settings: { transaction_sample_rate: '0.9' }, - }; - const configProduction = { - service: { name: 'myservice', environment: 'production' }, - settings: { transaction_sample_rate: '0.9' }, - }; - let etag: string; - - before(async () => { - log.debug('creating agent configuration'); - await createConfiguration(config); - await createConfiguration(configProduction); - }); - - after(async () => { - await deleteConfiguration(config); - await deleteConfiguration(configProduction); - }); - - it(`should have 'applied_by_agent=false' before supplying etag`, async () => { - const res1 = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - }); - - etag = res1.body._source.etag; - - const res2 = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - etag, - }); - - expect(res1.body._source.applied_by_agent).to.be(false); - expect(res2.body._source.applied_by_agent).to.be(false); - }); - - it(`should have 'applied_by_agent=true' after supplying etag`, async () => { - await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - etag, - }); - - async function hasBeenAppliedByAgent() { - const { body } = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - }); - - return body._source.applied_by_agent; - } - - // wait until `applied_by_agent` has been updated in elasticsearch - expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); - }); - it(`should have 'applied_by_agent=false' before marking as applied`, async () => { - const res1 = await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - }); - - expect(res1.body._source.applied_by_agent).to.be(false); - }); - it(`should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { - await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - mark_as_applied_by_agent: true, - }); - - async function hasBeenAppliedByAgent() { - const { body } = await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - }); - - return body._source.applied_by_agent; - } - - // wait until `applied_by_agent` has been updated in elasticsearch - expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); - }); - }); - }); -} - -async function waitFor(cb: () => Promise, retries = 50): Promise { - if (retries === 0) { - throw new Error(`Maximum number of retries reached`); - } - - const res = await cb(); - if (!res) { - await new Promise((resolve) => setTimeout(resolve, 100)); - return waitFor(cb, retries - 1); - } - return res; -} - -function omitTimestamp(configs: AgentConfigurationIntake[]) { - return configs.map((config: AgentConfigurationIntake) => omit(config, '@timestamp')); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts deleted file mode 100644 index 5630bd195b6cd..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.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 { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const noAccessUser = getService('supertestAsNoAccessUser'); - - function getAnomalyDetectionJobs() { - return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return noAccessUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user does not have read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts deleted file mode 100644 index 30e097e791eaa..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmReadUser = getService('supertestAsApmReadUser'); - - function getAnomalyDetectionJobs() { - return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return apmReadUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts deleted file mode 100644 index 15659229a1917..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts +++ /dev/null @@ -1,50 +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 { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmWriteUser = getService('supertestAsApmWriteUser'); - - function getAnomalyDetectionJobs() { - return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return apmWriteUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has write access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts deleted file mode 100644 index 8ac5566fc2c49..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import { CustomLink } from '../../../../../plugins/apm/common/custom_link/custom_link_types'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestWrite = getService('supertestAsApmWriteUser'); - - describe('custom links', () => { - it('is only be available to users with Gold license (or higher)', async () => { - const customLink = { - url: 'https://elastic.co', - label: 'with filters', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - } as CustomLink; - const response = await supertestWrite - .post(`/api/apm/settings/custom_links`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - expect(response.status).to.be(403); - - expectSnapshot(response.body.message).toMatchInline( - `"To create custom links, you must be subscribed to an Elastic Gold license or above. With it, you'll have the ability to create custom links to improve your workflow when analyzing your services."` - ); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts deleted file mode 100644 index 947defca05d94..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts +++ /dev/null @@ -1,123 +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 archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - const transactionType = 'request'; - const transactionName = 'GET /api'; - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('Breakdown', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - expect(response.status).to.be(200); - expect(response.body).to.eql({ timeseries: [] }); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the transaction breakdown for a service', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns the transaction breakdown for a transaction group', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}&transactionName=${transactionName}` - ); - - expect(response.status).to.be(200); - - const { timeseries } = response.body; - - const numberOfSeries = timeseries.length; - - expectSnapshot(numberOfSeries).toMatchInline(`1`); - - const { title, color, type, data, hideLegend, legendValue } = timeseries[0]; - - const nonNullDataPoints = data.filter((y: number | null) => y !== null); - - expectSnapshot(nonNullDataPoints.length).toMatchInline(`61`); - - expectSnapshot( - data.slice(0, 5).map(({ x, y }: { x: number; y: number | null }) => { - return { - x: new Date(x ?? NaN).toISOString(), - y, - }; - }) - ).toMatchInline(` - Array [ - Object { - "x": "2020-12-08T13:57:30.000Z", - "y": null, - }, - Object { - "x": "2020-12-08T13:58:00.000Z", - "y": null, - }, - Object { - "x": "2020-12-08T13:58:30.000Z", - "y": 1, - }, - Object { - "x": "2020-12-08T13:59:00.000Z", - "y": 1, - }, - Object { - "x": "2020-12-08T13:59:30.000Z", - "y": null, - }, - ] - `); - - expectSnapshot(title).toMatchInline(`"app"`); - expectSnapshot(color).toMatchInline(`"#54b399"`); - expectSnapshot(type).toMatchInline(`"areaStacked"`); - expectSnapshot(hideLegend).toMatchInline(`false`); - expectSnapshot(legendValue).toMatchInline(`"100%"`); - - expectSnapshot(data).toMatch(); - }); - it('returns the transaction breakdown sorted by name', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body.timeseries.map((serie: { title: string }) => serie.title)) - .toMatchInline(` - Array [ - "app", - "http", - "postgresql", - "redis", - ] - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts deleted file mode 100644 index 3088f4fd481d7..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts +++ /dev/null @@ -1,102 +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 archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); - - describe('Latency', () => { - describe('when data is not loaded ', () => { - it('returns 400 when latencyAggregationType is not informed', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` - ); - - expect(response.status).to.be(400); - }); - - it('returns 400 when transactionType is not informed', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - - expect(response.status).to.be(400); - }); - - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` - ); - - expect(response.status).to.be(200); - - expect(response.body.overallAvgDuration).to.be(null); - expect(response.body.latencyTimeseries.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - let response: PromiseReturnType; - - describe('average latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - - describe('95th percentile latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - - describe('99th percentile latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index f94bd6bd3be6f..08333de15ec6d 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -11,11 +11,12 @@ import path from 'path'; import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context'; import { PromiseReturnType } from '../../../plugins/observability/typings/common'; import { createApmUser, APM_TEST_PASSWORD, ApmUser } from './authentication'; +import { APMFtrConfigName } from '../configs'; +import { registry } from './registry'; -interface Settings { +interface Config { + name: APMFtrConfigName; license: 'basic' | 'trial'; - testFiles: string[]; - name: string; } const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async ( @@ -34,8 +35,8 @@ const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async return supertestAsPromised(url); }; -export function createTestConfig(settings: Settings) { - const { testFiles, license, name } = settings; +export function createTestConfig(config: Config) { + const { license, name } = config; return async ({ readConfigFile }: FtrConfigProviderContext) => { const xPackAPITestsConfig = await readConfigFile( @@ -47,8 +48,10 @@ export function createTestConfig(settings: Settings) { const supertestAsApmReadUser = supertestAsApmUser(servers.kibana, ApmUser.apmReadUser); + registry.init(config.name); + return { - testFiles, + testFiles: [require.resolve('../tests')], servers, esArchiver: { directory: path.resolve(__dirname, './fixtures/es_archiver'), @@ -69,7 +72,7 @@ export function createTestConfig(settings: Settings) { ), }, junit: { - reportName: name, + reportName: `APM API Integration tests (${name})`, }, esTestCluster: { ...xPackAPITestsConfig.get('esTestCluster'), diff --git a/x-pack/test/apm_api_integration/common/registry.ts b/x-pack/test/apm_api_integration/common/registry.ts new file mode 100644 index 0000000000000..8c918eae5a5a8 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/registry.ts @@ -0,0 +1,160 @@ +/* + * 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 { castArray, groupBy } from 'lodash'; +import callsites from 'callsites'; +import { maybe } from '../../../plugins/apm/common/utils/maybe'; +import { joinByKey } from '../../../plugins/apm/common/utils/join_by_key'; +import { APMFtrConfigName } from '../configs'; +import { FtrProviderContext } from './ftr_provider_context'; + +type ArchiveName = + | 'apm_8.0.0' + | '8.0.0' + | 'metrics_8.0.0' + | 'ml_8.0.0' + | 'observability_overview' + | 'rum_8.0.0' + | 'rum_test_data'; + +interface RunCondition { + config: APMFtrConfigName; + archives: ArchiveName[]; +} + +const callbacks: Array< + RunCondition & { + runs: Array<{ + cb: () => void; + }>; + } +> = []; + +let configName: APMFtrConfigName | undefined; + +let running: boolean = false; + +export const registry = { + init: (config: APMFtrConfigName) => { + configName = config; + callbacks.length = 0; + running = false; + }, + when: ( + title: string, + conditions: RunCondition | RunCondition[], + callback: (condition: RunCondition) => void + ) => { + const allConditions = castArray(conditions); + + if (!allConditions.length) { + throw new Error('At least one condition should be defined'); + } + + if (running) { + throw new Error("Can't add tests when running"); + } + + const frame = maybe(callsites()[1]); + + const file = frame?.getFileName(); + + if (!file) { + throw new Error('Could not infer file for suite'); + } + + allConditions.forEach((matchedCondition) => { + callbacks.push({ + ...matchedCondition, + runs: [ + { + cb: () => { + const suite = describe(title, () => { + callback(matchedCondition); + }); + + suite.file = file; + suite.eachTest((test) => { + test.file = file; + }); + }, + }, + ], + }); + }); + }, + run: (context: FtrProviderContext) => { + if (!configName) { + throw new Error(`registry was not init() before running`); + } + running = true; + const esArchiver = context.getService('esArchiver'); + const logger = context.getService('log'); + const logWithTimer = () => { + const start = process.hrtime(); + + return (message: string) => { + const diff = process.hrtime(start); + const time = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`; + logger.info(`(${time}) ${message}`); + }; + }; + + const groups = joinByKey(callbacks, ['config', 'archives'], (a, b) => ({ + ...a, + ...b, + runs: a.runs.concat(b.runs), + })); + + callbacks.length = 0; + + const byConfig = groupBy(groups, 'config'); + + Object.keys(byConfig).forEach((config) => { + const groupsForConfig = byConfig[config]; + // register suites for other configs, but skip them so tests are marked as such + // and their snapshots are not marked as obsolete + (config === configName ? describe : describe.skip)(config, () => { + groupsForConfig.forEach((group) => { + const { runs, ...condition } = group; + + const runBefore = async () => { + const log = logWithTimer(); + for (const archiveName of condition.archives) { + log(`Loading ${archiveName}`); + await esArchiver.load(archiveName); + } + if (condition.archives.length) { + log('Loaded all archives'); + } + }; + + const runAfter = async () => { + const log = logWithTimer(); + for (const archiveName of condition.archives) { + log(`Unloading ${archiveName}`); + await esArchiver.unload(archiveName); + } + if (condition.archives.length) { + log('Unloaded all archives'); + } + }; + + describe(condition.archives.join(',') || 'no data', () => { + before(runBefore); + + runs.forEach((run) => { + run.cb(); + }); + + after(runAfter); + }); + }); + }); + }); + + running = false; + }, +}; diff --git a/x-pack/test/apm_api_integration/configs/index.ts b/x-pack/test/apm_api_integration/configs/index.ts new file mode 100644 index 0000000000000..4bda5419c8729 --- /dev/null +++ b/x-pack/test/apm_api_integration/configs/index.ts @@ -0,0 +1,25 @@ +/* + * 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 { mapValues } from 'lodash'; +import { createTestConfig } from '../common/config'; + +const apmFtrConfigs = { + basic: { + license: 'basic' as const, + }, + trial: { + license: 'trial' as const, + }, +}; + +export type APMFtrConfigName = keyof typeof apmFtrConfigs; + +export const configs = mapValues(apmFtrConfigs, (value, key) => { + return createTestConfig({ + name: key as APMFtrConfigName, + ...value, + }); +}); diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts new file mode 100644 index 0000000000000..2b14e3b8a4a75 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts @@ -0,0 +1,70 @@ +/* + * 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 { format } from 'url'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const { end } = archives[archiveName]; + const start = new Date(Date.parse(end) - 600000).toISOString(); + + const apis = [ + { + pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', + params: { transactionType: 'request' }, + }, + { pathname: '/api/apm/alerts/chart_preview/transaction_error_count', params: {} }, + { + pathname: '/api/apm/alerts/chart_preview/transaction_duration', + params: { transactionType: 'request' }, + }, + ]; + + apis.forEach((api) => { + const url = format({ + pathname: api.pathname, + query: { + start, + end, + serviceName: 'opbeans-java', + ...api.params, + }, + }); + + registry.when( + `GET ${api.pathname} without data loaded`, + { config: 'basic', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + } + ); + + registry.when( + `GET ${api.pathname} with data loaded`, + { config: 'basic', archives: [archiveName] }, + () => { + it('returns the correct data', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect( + response.body.some((item: { x: number; y: number | null }) => item.x && item.y) + ).to.equal(true); + }); + } + ); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts new file mode 100644 index 0000000000000..2439943a664ea --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts @@ -0,0 +1,88 @@ +/* + * 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 { format } from 'url'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const range = archives_metadata[archiveName]; + + const url = format({ + pathname: `/api/apm/correlations/slow_transactions`, + query: { + start: range.start, + end: range.end, + durationPercentile: 95, + fieldNames: + 'user.username,user.id,host.ip,user_agent.name,kubernetes.pod.uuid,url.domain,container.id,service.node.name', + }, + }); + + registry.when('without data', { config: 'trial', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body.response).to.be(undefined); + }); + }); + + registry.when('with data and default args', { config: 'trial', archives: ['apm_8.0.0'] }, () => { + type ResponseBody = APIReturnType<'GET /api/apm/correlations/slow_transactions'>; + let response: { + status: number; + body: NonNullable; + }; + + before(async () => { + response = await supertest.get(url); + }); + + it('returns successfully', () => { + expect(response.status).to.eql(200); + }); + + it('returns significant terms', () => { + const sorted = response.body?.significantTerms?.sort(); + expectSnapshot(sorted?.map((term) => term.fieldName)).toMatchInline(` + Array [ + "user_agent.name", + "url.domain", + "host.ip", + "service.node.name", + "container.id", + "url.domain", + "user_agent.name", + ] + `); + }); + + it('returns a distribution per term', () => { + expectSnapshot(response.body?.significantTerms?.map((term) => term.distribution.length)) + .toMatchInline(` + Array [ + 11, + 11, + 11, + 11, + 11, + 11, + 11, + ] + `); + }); + + it('returns overall distribution', () => { + expectSnapshot(response.body?.overall?.distribution.length).toMatchInline(`11`); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap similarity index 95% rename from x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap rename to x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap index c8681866169a5..e21069ba2f0a6 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap +++ b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) CSM UX page load dist when there is data returns page load distribution 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution 1`] = ` Object { "maxDuration": 54.46, "minDuration": 0, @@ -456,7 +456,7 @@ Object { } `; -exports[`APM specs (trial) CSM UX page load dist when there is data returns page load distribution with breakdown 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution with breakdown 1`] = ` Array [ Object { "data": Array [ @@ -819,6 +819,6 @@ Array [ ] `; -exports[`APM specs (trial) CSM UX page load dist when there is no data returns empty list 1`] = `Object {}`; +exports[`APM API tests trial no data UX page load dist without data returns empty list 1`] = `Object {}`; -exports[`APM specs (trial) CSM UX page load dist when there is no data returns empty list with breakdowns 1`] = `Object {}`; +exports[`APM API tests trial no data UX page load dist without data returns empty list with breakdowns 1`] = `Object {}`; diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap similarity index 90% rename from x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap rename to x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap index 76e5180ba2141..01d0d7fb6139c 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap +++ b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) CSM CSM page views when there is data returns page views 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 CSM page views with data returns page views 1`] = ` Object { "items": Array [ Object { @@ -128,7 +128,7 @@ Object { } `; -exports[`APM specs (trial) CSM CSM page views when there is data returns page views with breakdown 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 CSM page views with data returns page views with breakdown 1`] = ` Object { "items": Array [ Object { @@ -265,14 +265,14 @@ Object { } `; -exports[`APM specs (trial) CSM CSM page views when there is no data returns empty list 1`] = ` +exports[`APM API tests trial no data CSM page views without data returns empty list 1`] = ` Object { "items": Array [], "topItems": Array [], } `; -exports[`APM specs (trial) CSM CSM page views when there is no data returns empty list with breakdowns 1`] = ` +exports[`APM API tests trial no data CSM page views without data returns empty list with breakdowns 1`] = ` Object { "items": Array [], "topItems": Array [], diff --git a/x-pack/test/apm_api_integration/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts new file mode 100644 index 0000000000000..6460c0832a947 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/csm_services.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM Services without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + + registry.when( + 'CSM services with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns rum services list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(`Array []`); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts similarity index 53% rename from x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts rename to x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts index f2033e03f5821..66f99dd29df5a 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts +++ b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts @@ -5,38 +5,31 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumHasDataApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM has rum data api', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=' - ); + registry.when('has_rum_data without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=' + ); - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` Object { "hasData": false, } `); - }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'has RUM data with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns that it has data and service name with most traffice', async () => { const response = await supertest.get( '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=' @@ -51,6 +44,6 @@ export default function rumHasDataApiTests({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts b/x-pack/test/apm_api_integration/tests/csm/js_errors.ts similarity index 72% rename from x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts rename to x-pack/test/apm_api_integration/tests/csm/js_errors.ts index 6abb701f98a1c..dcadc8424ef7e 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts +++ b/x-pack/test/apm_api_integration/tests/csm/js_errors.ts @@ -5,40 +5,33 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM js errors', () => { - describe('when there is no data', () => { - it('returns no js errors', async () => { - const response = await supertest.get( - '/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); + registry.when('CSM JS errors with data', { config: 'trial', archives: [] }, () => { + it('returns no js errors', async () => { + const response = await supertest.get( + '/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` Object { "totalErrorGroups": 0, "totalErrorPages": 0, "totalErrors": 0, } `); - }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_test_data'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_test_data'); - }); - + registry.when( + 'CSM JS errors without data', + { config: 'trial', archives: ['8.0.0', 'rum_test_data'] }, + () => { it('returns js errors', async () => { const response = await supertest.get( '/api/apm/rum-client/js-errors?start=2021-01-18T12%3A20%3A17.202Z&end=2021-01-18T12%3A25%3A17.203Z&uiFilters=%7B%22environment%22%3A%22ENVIRONMENT_ALL%22%2C%22serviceName%22%3A%5B%22elastic-co-frontend%22%5D%7D&pageSize=5&pageIndex=0' @@ -81,6 +74,6 @@ export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts similarity index 50% rename from x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts rename to x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts index 6db5de24baa99..0da0889c11775 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts +++ b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts @@ -5,38 +5,31 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM long task metrics', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql({ - longestLongTask: 0, - noOfLongTasks: 0, - sumOfLongTasks: 0, - }); + registry.when('CSM long task metrics without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql({ + longestLongTask: 0, + noOfLongTasks: 0, + sumOfLongTasks: 0, }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'CSM long task metrics with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns web core vitals values', async () => { const response = await supertest.get( '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' @@ -52,6 +45,6 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts new file mode 100644 index 0000000000000..28fa9cb87f516 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts @@ -0,0 +1,58 @@ +/* + * 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 { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('UX page load dist without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdown=Browser' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + }); + + registry.when( + 'UX page load dist with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns page load distribution', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + it('returns page load distribution with breakdown', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdown=Browser' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/tests/csm/page_views.ts new file mode 100644 index 0000000000000..43f9d278f694a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/page_views.ts @@ -0,0 +1,58 @@ +/* + * 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 { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM page views without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + }); + + registry.when( + 'CSM page views with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns page views', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + it('returns page views with breakdown', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/tests/csm/url_search.ts new file mode 100644 index 0000000000000..906f1783cb90e --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/url_search.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM url search api without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [], + "total": 0, + } + `); + }); + }); + + registry.when( + 'CSM url search api with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns top urls when no query', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [ + Object { + "count": 5, + "pld": 4924000, + "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", + }, + Object { + "count": 1, + "pld": 2760000, + "url": "http://localhost:5601/nfw/app/home", + }, + ], + "total": 2, + } + `); + }); + + it('returns specific results against query', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&urlQuery=csm&percentile=50' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [ + Object { + "count": 5, + "pld": 4924000, + "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", + }, + ], + "total": 1, + } + `); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts similarity index 55% rename from x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts rename to x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts index 50c261d2d37ad..3574ef065eef7 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts +++ b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts @@ -5,41 +5,34 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM web core vitals', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' - ); + registry.when('CSM web core vitals without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({ - coreVitalPages: 0, - cls: null, - tbt: 0, - lcpRanks: [100, 0, 0], - fidRanks: [100, 0, 0], - clsRanks: [100, 0, 0], - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({ + coreVitalPages: 0, + cls: null, + tbt: 0, + lcpRanks: [100, 0, 0], + fidRanks: [100, 0, 0], + clsRanks: [100, 0, 0], }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'CSM web core vitals with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns web core vitals values', async () => { const response = await supertest.get( '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' @@ -73,6 +66,6 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts similarity index 98% rename from x-pack/test/apm_api_integration/basic/tests/feature_controls.ts rename to x-pack/test/apm_api_integration/tests/feature_controls.ts index 35025fcbfd107..7a65d8114c73f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -5,7 +5,8 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../common/ftr_provider_context'; +import { registry } from '../common/registry'; export default function featureControlsTests({ getService }: FtrProviderContext) { const supertest = getService('supertestAsApmWriteUser'); @@ -270,7 +271,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) } } - describe('apm feature controls', () => { + registry.when('apm feature controls', { config: 'basic', archives: [] }, () => { const config = { service: { name: 'test-service' }, settings: { transaction_sample_rate: '0.5' }, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts new file mode 100644 index 0000000000000..eef82c714b2d0 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -0,0 +1,70 @@ +/* + * 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 { FtrProviderContext } from '../common/ftr_provider_context'; +import { registry } from '../common/registry'; + +export default function apmApiIntegrationTests(providerContext: FtrProviderContext) { + const { loadTestFile } = providerContext; + + describe('APM API tests', function () { + this.tags('ciGroup1'); + loadTestFile(require.resolve('./alerts/chart_preview')); + + loadTestFile(require.resolve('./correlations/slow_transactions')); + + loadTestFile(require.resolve('./csm/csm_services')); + loadTestFile(require.resolve('./csm/has_rum_data')); + loadTestFile(require.resolve('./csm/js_errors')); + loadTestFile(require.resolve('./csm/long_task_metrics')); + loadTestFile(require.resolve('./csm/page_load_dist')); + loadTestFile(require.resolve('./csm/page_views')); + loadTestFile(require.resolve('./csm/url_search')); + loadTestFile(require.resolve('./csm/web_core_vitals')); + + loadTestFile(require.resolve('./metrics_charts/metrics_charts')); + + loadTestFile(require.resolve('./observability_overview/has_data')); + loadTestFile(require.resolve('./observability_overview/observability_overview')); + + loadTestFile(require.resolve('./service_maps/service_maps')); + + loadTestFile(require.resolve('./service_overview/dependencies')); + loadTestFile(require.resolve('./service_overview/error_groups')); + loadTestFile(require.resolve('./service_overview/instances')); + + loadTestFile(require.resolve('./services/agent_name')); + loadTestFile(require.resolve('./services/annotations')); + loadTestFile(require.resolve('./services/service_details')); + loadTestFile(require.resolve('./services/service_icons')); + loadTestFile(require.resolve('./services/throughput')); + loadTestFile(require.resolve('./services/top_services')); + loadTestFile(require.resolve('./services/transaction_types')); + + loadTestFile(require.resolve('./settings/anomaly_detection/basic')); + loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); + loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); + loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); + + loadTestFile(require.resolve('./settings/agent_configuration')); + + loadTestFile(require.resolve('./settings/custom_link')); + + loadTestFile(require.resolve('./traces/top_traces')); + + loadTestFile(require.resolve('./transactions/breakdown')); + loadTestFile(require.resolve('./transactions/distribution')); + loadTestFile(require.resolve('./transactions/error_rate')); + loadTestFile(require.resolve('./transactions/latency')); + loadTestFile(require.resolve('./transactions/throughput')); + loadTestFile(require.resolve('./transactions/top_transaction_groups')); + loadTestFile(require.resolve('./transactions/transactions_groups_overview')); + + loadTestFile(require.resolve('./feature_controls')); + + registry.run(providerContext); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts new file mode 100644 index 0000000000000..dda46f00d7c72 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts @@ -0,0 +1,446 @@ +/* + * 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 { first } from 'lodash'; +import { MetricsChartsByAgentAPIResponse } from '../../../../plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent'; +import { GenericMetricsChart } from '../../../../plugins/apm/server/lib/metrics/transform_metrics_chart'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +interface ChartResponse { + body: MetricsChartsByAgentAPIResponse; + status: number; +} + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when( + 'Metrics charts when data is loaded', + { config: 'basic', archives: ['metrics_8.0.0'] }, + () => { + describe('for opbeans-node', () => { + const start = encodeURIComponent('2020-09-08T14:50:00.000Z'); + const end = encodeURIComponent('2020-09-08T14:55:00.000Z'); + const uiFilters = encodeURIComponent(JSON.stringify({})); + const agentName = 'nodejs'; + + describe('returns metrics data', () => { + let chartsResponse: ChartResponse; + before(async () => { + chartsResponse = await supertest.get( + `/api/apm/services/opbeans-node/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + }); + it('contains CPU usage and System memory usage chart data', async () => { + expect(chartsResponse.status).to.be(200); + expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` + Array [ + "CPU usage", + "System memory usage", + ] + `); + }); + + describe('CPU usage', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'cpu_usage_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "System max", + "System average", + "Process max", + "Process average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.714, + 0.3877, + 0.75, + 0.2543, + ] + `); + }); + }); + + describe("System memory usage (using 'system.memory' fields to calculate the memory usage)", () => { + let systemMemoryUsageChart: GenericMetricsChart | undefined; + before(() => { + systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + }); + + it('has correct series', () => { + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)) + .toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.722093920925555, + 0.718173546796348, + ] + `); + }); + }); + }); + }); + + describe('for opbeans-java', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); + const agentName = 'java'; + + describe('returns metrics data', () => { + const start = encodeURIComponent('2020-09-08T14:55:30.000Z'); + const end = encodeURIComponent('2020-09-08T15:00:00.000Z'); + + let chartsResponse: ChartResponse; + before(async () => { + chartsResponse = await supertest.get( + `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + }); + + it('has correct chart data', async () => { + expect(chartsResponse.status).to.be(200); + expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` + Array [ + "CPU usage", + "System memory usage", + "Heap Memory", + "Non-Heap Memory", + "Thread Count", + "Garbage collection per minute", + "Garbage collection time spent per minute", + ] + `); + }); + + describe('CPU usage', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'cpu_usage_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "System max", + "System average", + "Process max", + "Process average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.203, + 0.178777777777778, + 0.01, + 0.009, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.193, + 0.193, + 0.009, + 0.009, + ] + `); + }); + }); + + describe("System memory usage (using 'system.process.cgroup' fields to calculate the memory usage)", () => { + let systemMemoryUsageChart: GenericMetricsChart | undefined; + before(() => { + systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + }); + + it('has correct series', () => { + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)) + .toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.707924703557837, + 0.705395980841182, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.707924703557837, + 0.707924703557837, + ] + `); + }); + }); + + describe('Heap Memory', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'heap_memory_area_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. used", + "Avg. committed", + "Avg. limit", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 222501617.777778, + 374341632, + 1560281088, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 211472896, + 374341632, + 1560281088, + ] + `); + }); + }); + + describe('Non-Heap Memory', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'non_heap_memory_area_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. used", + "Avg. committed", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 138573397.333333, + 147677639.111111, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 138162752, + 147386368, + ] + `); + }); + }); + + describe('Thread Count', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'thread_count_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. count", + "Max count", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 44.4444444444444, + 45, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 44, + 44, + ] + `); + }); + }); + + describe('Garbage collection per minute', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'gc_rate_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "G1 Old Generation", + "G1 Young Generation", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0, + 15, + ] + `); + }); + }); + + describe('Garbage collection time spent per minute', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'gc_time_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "G1 Old Generation", + "G1 Young Generation", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0, + 187.5, + ] + `); + }); + }); + }); + + // 9223372036854771712 = memory limit for a c-group when no memory limit is specified + it('calculates system memory usage using system total field when cgroup limit is equal to 9223372036854771712', async () => { + const start = encodeURIComponent('2020-09-08T15:00:30.000Z'); + const end = encodeURIComponent('2020-09-08T15:05:00.000Z'); + + const chartsResponse: ChartResponse = await supertest.get( + `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + + const systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.114523896426499, + 0.114002376090415, + ] + `); + + const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.11383724014064, + 0.11383724014064, + ] + `); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts similarity index 67% rename from x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts rename to x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts index 6d0d2d3042625..9c88f75c6adbd 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts @@ -4,40 +4,45 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; - - describe('Has data', () => { - describe('when data is not loaded', () => { + registry.when( + 'Observability overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('returns false when there is no data', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(false); }); - }); - describe('when only onboarding data is loaded', () => { - before(() => esArchiver.load('observability_overview')); - after(() => esArchiver.unload('observability_overview')); + } + ); + + registry.when( + 'Observability overview when only onboarding data is loaded', + { config: 'basic', archives: ['observability_overview'] }, + () => { it('returns false when there is only onboarding data', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(false); }); - }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Observability overview when APM data is loaded', + { config: 'basic', archives: ['apm_8.0.0'] }, + () => { it('returns true when there is at least one document on transaction, error or metrics indices', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(true); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts similarity index 69% rename from x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts rename to x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts index f7c459029c7f5..7c9d8fa8b0f91 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -19,22 +19,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = encodeURIComponent(metadata.end); const bucketSize = '60s'; - describe('Observability overview', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` - ); - expect(response.status).to.be(200); + registry.when( + 'Observability overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` + ); + expect(response.status).to.be(200); - expect(response.body.serviceCount).to.be(0); - expect(response.body.transactionCoordinates.length).to.be(0); + expect(response.body.serviceCount).to.be(0); + expect(response.body.transactionCoordinates.length).to.be(0); + }); }); - }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Observability overview when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the service count and transaction coordinates', async () => { const response = await supertest.get( `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` @@ -80,6 +86,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap b/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap similarity index 99% rename from x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap rename to x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap index 7639822eaa6f9..69bc039b67ed2 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap +++ b/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) Service Maps Service Maps with a trial license /api/apm/service-map when there is data returns service map elements filtering by environment not defined 1`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map returns service map elements filtering by environment not defined 1`] = ` Object { "elements": Array [ Object { @@ -514,7 +514,7 @@ Object { } `; -exports[`APM specs (trial) Service Maps Service Maps with a trial license /api/apm/service-map when there is data returns the correct data 3`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map returns the correct data 3`] = ` Array [ Object { "data": Object { @@ -1741,7 +1741,7 @@ Array [ ] `; -exports[`APM specs (trial) Service Maps Service Maps with a trial license when there is data with anomalies with the default apm user returns the correct anomaly stats 3`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map with ML data with the default apm user returns the correct anomaly stats 3`] = ` Object { "elements": Array [ Object { diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts similarity index 58% rename from x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts rename to x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts index 02acd34ad5666..a15f0442c7cde 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts @@ -7,57 +7,85 @@ import querystring from 'querystring'; import expect from '@kbn/expect'; import { isEmpty, uniq } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function serviceMapsApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; const start = encodeURIComponent(metadata.start); const end = encodeURIComponent(metadata.end); - describe('Service Maps with a trial license', () => { + registry.when('Service map with a basic license', { config: 'basic', archives: [] }, () => { + it('is only be available to users with Platinum license (or higher)', async () => { + const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + + expect(response.status).to.be(403); + + expectSnapshot(response.body.message).toMatchInline( + `"In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data."` + ); + }); + }); + + registry.when('Service map without data', { config: 'trial', archives: [] }, () => { describe('/api/apm/service-map', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + it('returns an empty list', async () => { + const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - expect(response.status).to.be(200); - expect(response.body.elements.length).to.be(0); - }); + expect(response.status).to.be(200); + expect(response.body.elements.length).to.be(0); }); + }); - describe('when there is data', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + describe('/api/apm/service-map/service/{serviceName}', () => { + it('returns an object with nulls', async () => { + const q = querystring.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + }); + const response = await supertest.get(`/api/apm/service-map/service/opbeans-node?${q}`); - let response: PromiseReturnType; + expect(response.status).to.be(200); - before(async () => { - response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - }); + expect(response.body.avgCpuUsage).to.be(null); + expect(response.body.avgErrorRate).to.be(null); + expect(response.body.avgMemoryUsage).to.be(null); + expect(response.body.transactionStats.avgRequestsPerMinute).to.be(null); + expect(response.body.transactionStats.avgTransactionDuration).to.be(null); + }); + }); + }); - it('returns service map elements', () => { - expect(response.status).to.be(200); - expect(response.body.elements.length).to.be.greaterThan(0); - }); + registry.when('Service Map with data', { config: 'trial', archives: ['apm_8.0.0'] }, () => { + describe('/api/apm/service-map', () => { + let response: PromiseReturnType; + + before(async () => { + response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + }); + + it('returns service map elements', () => { + expect(response.status).to.be(200); + expect(response.body.elements.length).to.be.greaterThan(0); + }); - it('returns the correct data', () => { - const elements: Array<{ data: Record }> = response.body.elements; + it('returns the correct data', () => { + const elements: Array<{ data: Record }> = response.body.elements; - const serviceNames = uniq( - elements - .filter((element) => element.data['service.name'] !== undefined) - .map((element) => element.data['service.name']) - ).sort(); + const serviceNames = uniq( + elements + .filter((element) => element.data['service.name'] !== undefined) + .map((element) => element.data['service.name']) + ).sort(); - expectSnapshot(serviceNames).toMatchInline(` + expectSnapshot(serviceNames).toMatchInline(` Array [ "kibana", "kibana-frontend", @@ -71,13 +99,13 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) ] `); - const externalDestinations = uniq( - elements - .filter((element) => element.data.target?.startsWith('>')) - .map((element) => element.data.target) - ).sort(); + const externalDestinations = uniq( + elements + .filter((element) => element.data.target?.startsWith('>')) + .map((element) => element.data.target) + ).sort(); - expectSnapshot(externalDestinations).toMatchInline(` + expectSnapshot(externalDestinations).toMatchInline(` Array [ ">elasticsearch", ">feeds.elastic.co:443", @@ -86,51 +114,26 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) ] `); - expectSnapshot(elements).toMatch(); - }); - - it('returns service map elements filtering by environment not defined', async () => { - const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED'; - const { body, status } = await supertest.get( - `/api/apm/service-map?start=${start}&end=${end}&environment=${ENVIRONMENT_NOT_DEFINED}` - ); - expect(status).to.be(200); - const environments = new Set(); - body.elements.forEach((element: { data: Record }) => { - environments.add(element.data['service.environment']); - }); - - expect(environments.has(ENVIRONMENT_NOT_DEFINED)).to.eql(true); - expectSnapshot(body).toMatch(); - }); + expectSnapshot(elements).toMatch(); }); - }); - - describe('/api/apm/service-map/service/{serviceName}', () => { - describe('when there is no data', () => { - it('returns an object with nulls', async () => { - const q = querystring.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - }); - const response = await supertest.get(`/api/apm/service-map/service/opbeans-node?${q}`); - expect(response.status).to.be(200); - - expect(response.body.avgCpuUsage).to.be(null); - expect(response.body.avgErrorRate).to.be(null); - expect(response.body.avgMemoryUsage).to.be(null); - expect(response.body.transactionStats.avgRequestsPerMinute).to.be(null); - expect(response.body.transactionStats.avgTransactionDuration).to.be(null); + it('returns service map elements filtering by environment not defined', async () => { + const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED'; + const { body, status } = await supertest.get( + `/api/apm/service-map?start=${start}&end=${end}&environment=${ENVIRONMENT_NOT_DEFINED}` + ); + expect(status).to.be(200); + const environments = new Set(); + body.elements.forEach((element: { data: Record }) => { + environments.add(element.data['service.environment']); }); + + expect(environments.has(ENVIRONMENT_NOT_DEFINED)).to.eql(true); + expectSnapshot(body).toMatch(); }); }); - describe('when there is data with anomalies', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + describe('/api/apm/service-map with ML data', () => { describe('with the default apm user', () => { let response: PromiseReturnType; diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/es_utils.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/es_utils.ts similarity index 100% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/es_utils.ts rename to x-pack/test/apm_api_integration/tests/service_overview/dependencies/es_utils.ts diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts similarity index 91% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts rename to x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts index aeb5d1256796a..b3e7e0672fc7f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts @@ -8,26 +8,28 @@ import expect from '@kbn/expect'; import url from 'url'; import { sortBy, pick, last } from 'lodash'; import { ValuesType } from 'utility-types'; -import { Maybe } from '../../../../../../plugins/apm/typings/common'; -import { isFiniteNumber } from '../../../../../../plugins/apm/common/utils/is_finite_number'; -import { APIReturnType } from '../../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { ENVIRONMENT_ALL } from '../../../../../../plugins/apm/common/environment_filter_values'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import archives from '../../../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../../common/registry'; +import { Maybe } from '../../../../../plugins/apm/typings/common'; +import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; +import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { ENVIRONMENT_ALL } from '../../../../../plugins/apm/common/environment_filter_values'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives from '../../../common/fixtures/es_archiver/archives_metadata'; import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils'; const round = (num: Maybe): string => (isFiniteNumber(num) ? num.toPrecision(4) : ''); export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const es = getService('es'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service overview dependencies', () => { - describe('when data is not loaded', () => { + registry.when( + 'Service overview dependencies when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -44,9 +46,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body).to.eql([]); }); - }); + } + ); - describe('when specific data is loaded', () => { + registry.when( + 'Service overview dependencies when specific data is loaded', + { config: 'basic', archives: [] }, + () => { let response: { status: number; body: APIReturnType<'GET /api/apm/services/{serviceName}/dependencies'>; @@ -285,17 +291,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { impact: 0, }); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Service overview dependencies when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: { status: number; body: APIReturnType<'GET /api/apm/services/{serviceName}/dependencies'>; }; before(async () => { - await esArchiver.load(archiveName); - response = await supertest.get( url.format({ pathname: `/api/apm/services/opbeans-java/dependencies`, @@ -309,8 +317,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); }); - after(() => esArchiver.unload(archiveName)); - it('returns a successful response', () => { expect(response.status).to.be(200); }); @@ -380,6 +386,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts b/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts similarity index 94% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts rename to x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts index 7d1c05960f3e6..fc649c60103c8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts @@ -7,18 +7,20 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { pick, uniqBy } from 'lodash'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service overview error groups', () => { - describe('when data is not loaded', () => { + registry.when( + 'Service overview error groups when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ @@ -41,12 +43,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { is_aggregation_accurate: true, }); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Service overview error groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the correct data', async () => { const response = await supertest.get( `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ @@ -220,6 +223,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(uniqBy(items, 'group_id').length).to.eql(totalItems); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts similarity index 83% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts rename to x-pack/test/apm_api_integration/tests/service_overview/instances.ts index 2227a8c09a6cf..08d60f90900b8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts @@ -7,14 +7,14 @@ import expect from '@kbn/expect'; import url from 'url'; import { pick, sortBy } from 'lodash'; -import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; @@ -24,31 +24,36 @@ export default function ApiTest({ getService }: FtrProviderContext) { body: APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'>; } - describe('Service overview instances', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response: Response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - uiFilters: '{}', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); + registry.when( + 'Service overview instances when data is not loaded', + { config: 'basic', archives: [] }, + () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response: Response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/service_overview_instances`, + query: { + start, + end, + numBuckets: 20, + transactionType: 'request', + uiFilters: '{}', + }, + }) + ); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + } + ); + registry.when( + 'Service overview instances when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { describe('fetching java data', () => { let response: Response; @@ -209,6 +214,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(values); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap b/x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap similarity index 96% rename from x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap rename to x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap index fe7f434aad2e1..f23601fccb174 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap +++ b/x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Services Throughput when data is loaded returns the service throughput has the correct throughput 1`] = ` +exports[`APM API tests basic apm_8.0.0 Throughput when data is loaded has the correct throughput 1`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts b/x-pack/test/apm_api_integration/tests/services/agent_name.ts similarity index 55% rename from x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts rename to x-pack/test/apm_api_integration/tests/services/agent_name.ts index cea8fb5da2428..538d7a448ea30 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts +++ b/x-pack/test/apm_api_integration/tests/services/agent_name.ts @@ -5,34 +5,33 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const range = archives[archiveName]; const start = encodeURIComponent(range.start); const end = encodeURIComponent(range.end); - describe('Agent name', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` - ); + registry.when('Agent name when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({}); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Agent name when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the agent name', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` @@ -42,6 +41,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body).to.eql({ agentName: 'nodejs' }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/services/annotations.ts b/x-pack/test/apm_api_integration/tests/services/annotations.ts similarity index 92% rename from x-pack/test/apm_api_integration/trial/tests/services/annotations.ts rename to x-pack/test/apm_api_integration/tests/services/annotations.ts index c2ddc10c5f1d2..4ff690fa01aa1 100644 --- a/x-pack/test/apm_api_integration/trial/tests/services/annotations.ts +++ b/x-pack/test/apm_api_integration/tests/services/annotations.ts @@ -7,7 +7,8 @@ import expect from '@kbn/expect'; import { merge, cloneDeep, isPlainObject } from 'lodash'; import { JsonObject } from 'src/plugins/kibana_utils/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; const DEFAULT_INDEX_NAME = 'observability-annotations'; @@ -40,7 +41,32 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { } } - describe('APM annotations with a trial license', () => { + registry.when('Annotations with a basic license', { config: 'basic', archives: [] }, () => { + describe('when creating an annotation', () => { + it('fails with a 403 forbidden', async () => { + const response = await request({ + url: '/api/apm/services/opbeans-java/annotation', + method: 'POST', + data: { + '@timestamp': new Date().toISOString(), + message: 'New deployment', + tags: ['foo'], + service: { + version: '1.1', + environment: 'production', + }, + }, + }); + + expect(response.status).to.be(403); + expect(response.body.message).to.be( + 'Annotations require at least a gold license or a trial license.' + ); + }); + }); + }); + + registry.when('Annotations with a trial license', { config: 'trial', archives: [] }, () => { describe('when creating an annotation', () => { afterEach(async () => { const indexExists = (await es.indices.exists({ index: DEFAULT_INDEX_NAME })).body; diff --git a/x-pack/test/apm_api_integration/basic/tests/services/service_details.ts b/x-pack/test/apm_api_integration/tests/services/service_details.ts similarity index 87% rename from x-pack/test/apm_api_integration/basic/tests/services/service_details.ts rename to x-pack/test/apm_api_integration/tests/services/service_details.ts index 54bd16e6f78c4..77155c907f3b1 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/service_details.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_details.ts @@ -6,18 +6,20 @@ import expect from '@kbn/expect'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service details', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Service details when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -29,12 +31,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body).to.eql({}); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Service details when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns java service details', async () => { const response = await supertest.get( url.format({ @@ -116,6 +119,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts b/x-pack/test/apm_api_integration/tests/services/service_icons.ts similarity index 53% rename from x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts rename to x-pack/test/apm_api_integration/tests/services/service_icons.ts index 4b79de14551d6..7926a2744e45c 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_icons.ts @@ -6,35 +6,34 @@ import expect from '@kbn/expect'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service icons', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/metadata/icons`, - query: { start, end }, - }) - ); + registry.when('Service icons when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/metadata/icons`, + query: { start, end }, + }) + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({}); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Service icons when data is not loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns java service icons', async () => { const response = await supertest.get( url.format({ @@ -46,11 +45,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` - Object { - "agentName": "java", - "containerType": "Kubernetes", - } - `); + Object { + "agentName": "java", + "containerType": "Kubernetes", + } + `); }); it('returns python service icons', async () => { @@ -64,13 +63,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` - Object { - "agentName": "python", - "cloudProvider": "gcp", - "containerType": "Kubernetes", - } - `); + Object { + "agentName": "python", + "cloudProvider": "gcp", + "containerType": "Kubernetes", + } + `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.ts b/x-pack/test/apm_api_integration/tests/services/throughput.ts new file mode 100644 index 0000000000000..fa94cbf600709 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/throughput.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import qs from 'querystring'; +import { first, last } from 'lodash'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const metadata = archives_metadata[archiveName]; + + registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-java/throughput?${qs.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + transactionType: 'request', + })}` + ); + expect(response.status).to.be(200); + expect(response.body.throughput.length).to.be(0); + }); + }); + + registry.when( + 'Throughput when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let throughputResponse: { + throughput: Array<{ x: number; y: number | null }>; + }; + before(async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-java/throughput?${qs.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + transactionType: 'request', + })}` + ); + throughputResponse = response.body; + }); + + it('returns some data', () => { + expect(throughputResponse.throughput.length).to.be.greaterThan(0); + + const nonNullDataPoints = throughputResponse.throughput.filter(({ y }) => y !== null); + + expect(nonNullDataPoints.length).to.be.greaterThan(0); + }); + + it('has the correct start date', () => { + expectSnapshot( + new Date(first(throughputResponse.throughput)?.x ?? NaN).toISOString() + ).toMatchInline(`"2020-12-08T13:57:30.000Z"`); + }); + + it('has the correct end date', () => { + expectSnapshot( + new Date(last(throughputResponse.throughput)?.x ?? NaN).toISOString() + ).toMatchInline(`"2020-12-08T14:27:30.000Z"`); + }); + + it('has the correct number of buckets', () => { + expectSnapshot(throughputResponse.throughput.length).toMatchInline(`61`); + }); + + it('has the correct throughput', () => { + expectSnapshot(throughputResponse.throughput).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts new file mode 100644 index 0000000000000..42797e3e7c87a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -0,0 +1,364 @@ +/* + * 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 { sortBy, pick, isEmpty } from 'lodash'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); + + const archiveName = 'apm_8.0.0'; + + const range = archives_metadata[archiveName]; + + // url parameters + const start = encodeURIComponent(range.start); + const end = encodeURIComponent(range.end); + + const uiFilters = encodeURIComponent(JSON.stringify({})); + + registry.when( + 'APM Services Overview with a basic license when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + + expect(response.status).to.be(200); + expect(response.body.hasHistoricalData).to.be(false); + expect(response.body.hasLegacyData).to.be(false); + expect(response.body.items.length).to.be(0); + }); + } + ); + + registry.when( + 'APM Services Overview with a basic license when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let response: { + status: number; + body: APIReturnType<'GET /api/apm/services'>; + }; + + let sortedItems: typeof response.body.items; + + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + sortedItems = sortBy(response.body.items, 'serviceName'); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('returns hasHistoricalData: true', () => { + expect(response.body.hasHistoricalData).to.be(true); + }); + + it('returns hasLegacyData: false', () => { + expect(response.body.hasLegacyData).to.be(false); + }); + + it('returns the correct service names', () => { + expectSnapshot(sortedItems.map((item) => item.serviceName)).toMatchInline(` + Array [ + "kibana", + "kibana-frontend", + "opbeans-dotnet", + "opbeans-go", + "opbeans-java", + "opbeans-node", + "opbeans-python", + "opbeans-ruby", + "opbeans-rum", + ] + `); + }); + + it('returns the correct metrics averages', () => { + expectSnapshot( + sortedItems.map((item) => + pick( + item, + 'transactionErrorRate.value', + 'avgResponseTime.value', + 'transactionsPerMinute.value' + ) + ) + ).toMatchInline(` + Array [ + Object { + "avgResponseTime": Object { + "value": 420419.34550767, + }, + "transactionErrorRate": Object { + "value": 0, + }, + "transactionsPerMinute": Object { + "value": 45.6333333333333, + }, + }, + Object { + "avgResponseTime": Object { + "value": 2382833.33333333, + }, + "transactionErrorRate": Object { + "value": null, + }, + "transactionsPerMinute": Object { + "value": 0.2, + }, + }, + Object { + "avgResponseTime": Object { + "value": 631521.83908046, + }, + "transactionErrorRate": Object { + "value": 0.0229885057471264, + }, + "transactionsPerMinute": Object { + "value": 2.9, + }, + }, + Object { + "avgResponseTime": Object { + "value": 27946.1484375, + }, + "transactionErrorRate": Object { + "value": 0.015625, + }, + "transactionsPerMinute": Object { + "value": 4.26666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 237339.813333333, + }, + "transactionErrorRate": Object { + "value": 0.16, + }, + "transactionsPerMinute": Object { + "value": 2.5, + }, + }, + Object { + "avgResponseTime": Object { + "value": 24920.1052631579, + }, + "transactionErrorRate": Object { + "value": 0.0210526315789474, + }, + "transactionsPerMinute": Object { + "value": 3.16666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 29542.6607142857, + }, + "transactionErrorRate": Object { + "value": 0.0357142857142857, + }, + "transactionsPerMinute": Object { + "value": 1.86666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 70518.9328358209, + }, + "transactionErrorRate": Object { + "value": 0.0373134328358209, + }, + "transactionsPerMinute": Object { + "value": 4.46666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 2319812.5, + }, + "transactionErrorRate": Object { + "value": null, + }, + "transactionsPerMinute": Object { + "value": 0.533333333333333, + }, + }, + ] + `); + }); + + it('returns environments', () => { + expectSnapshot(sortedItems.map((item) => item.environments ?? [])).toMatchInline(` + Array [ + Array [ + "production", + ], + Array [ + "production", + ], + Array [ + "production", + ], + Array [ + "testing", + ], + Array [ + "production", + ], + Array [ + "testing", + ], + Array [], + Array [], + Array [ + "testing", + ], + ] + `); + }); + + it(`RUM services don't report any transaction error rates`, () => { + // RUM transactions don't have event.outcome set, + // so they should not have an error rate + + const rumServices = sortedItems.filter((item) => item.agentName === 'rum-js'); + + expect(rumServices.length).to.be.greaterThan(0); + + expect(rumServices.every((item) => isEmpty(item.transactionErrorRate?.value))); + }); + + it('non-RUM services all report transaction error rates', () => { + const nonRumServices = sortedItems.filter((item) => item.agentName !== 'rum-js'); + + expect( + nonRumServices.every((item) => { + return ( + typeof item.transactionErrorRate?.value === 'number' && + item.transactionErrorRate.timeseries.length > 0 + ); + }) + ).to.be(true); + }); + } + ); + + registry.when( + 'APM Services overview with a trial license when data is loaded', + { config: 'trial', archives: [archiveName] }, + () => { + describe('with the default APM read user', () => { + describe('and fetching a list of services', () => { + let response: { + status: number; + body: APIReturnType<'GET /api/apm/services'>; + }; + + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('there is at least one service', () => { + expect(response.body.items.length).to.be.greaterThan(0); + }); + + it('some items have a health status set', () => { + // Under the assumption that the loaded archive has + // at least one APM ML job, and the time range is longer + // than 15m, at least one items should have a health status + // set. Note that we currently have a bug where healthy + // services report as unknown (so without any health status): + // https://github.com/elastic/kibana/issues/77083 + + const healthStatuses = sortBy(response.body.items, 'serviceName').map( + (item: any) => item.healthStatus + ); + + expect(healthStatuses.filter(Boolean).length).to.be.greaterThan(0); + + expectSnapshot(healthStatuses).toMatchInline(` + Array [ + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + ] + `); + }); + }); + }); + + describe('with a user that does not have access to ML', () => { + let response: PromiseReturnType; + before(async () => { + response = await supertestAsApmReadUserWithoutMlAccess.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('there is at least one service', () => { + expect(response.body.items.length).to.be.greaterThan(0); + }); + + it('contains no health statuses', () => { + const definedHealthStatuses = response.body.items + .map((item: any) => item.healthStatus) + .filter(Boolean); + + expect(definedHealthStatuses.length).to.be(0); + }); + }); + + describe('and fetching a list of services with a filter', () => { + let response: PromiseReturnType; + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${encodeURIComponent( + `{"kuery":"service.name:opbeans-java","environment":"ENVIRONMENT_ALL"}` + )}` + ); + }); + + it('does not return health statuses for services that are not found in APM data', () => { + expect(response.status).to.be(200); + + expect(response.body.items.length).to.be(1); + + expect(response.body.items[0].serviceName).to.be('opbeans-java'); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts similarity index 75% rename from x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts rename to x-pack/test/apm_api_integration/tests/services/transaction_types.ts index fcfe1660d58e2..c45f4083ef8da 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts +++ b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts @@ -5,12 +5,12 @@ */ import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -19,8 +19,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const start = encodeURIComponent(metadata.start); const end = encodeURIComponent(metadata.end); - describe('Transaction types', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Transaction types when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` @@ -30,12 +32,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.transactionTypes.length).to.be(0); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Transaction types when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` @@ -53,6 +56,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts new file mode 100644 index 0000000000000..0b58dd5908c60 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts @@ -0,0 +1,495 @@ +/* + * 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 { omit, orderBy } from 'lodash'; +import { AgentConfigurationIntake } from '../../../../plugins/apm/common/agent_configuration/configuration_types'; +import { AgentConfigSearchParams } from '../../../../plugins/apm/server/routes/settings/agent_configuration'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function agentConfigurationTests({ getService }: FtrProviderContext) { + const supertestRead = getService('supertestAsApmReadUser'); + const supertestWrite = getService('supertestAsApmWriteUser'); + const log = getService('log'); + + const archiveName = 'apm_8.0.0'; + + function getServices() { + return supertestRead + .get(`/api/apm/settings/agent-configuration/services`) + .set('kbn-xsrf', 'foo'); + } + + function getEnvironments(serviceName: string) { + return supertestRead + .get(`/api/apm/settings/agent-configuration/environments?serviceName=${serviceName}`) + .set('kbn-xsrf', 'foo'); + } + + function getAgentName(serviceName: string) { + return supertestRead + .get(`/api/apm/settings/agent-configuration/agent_name?serviceName=${serviceName}`) + .set('kbn-xsrf', 'foo'); + } + + function searchConfigurations(configuration: AgentConfigSearchParams) { + return supertestRead + .post(`/api/apm/settings/agent-configuration/search`) + .send(configuration) + .set('kbn-xsrf', 'foo'); + } + + function getAllConfigurations() { + return supertestRead.get(`/api/apm/settings/agent-configuration`).set('kbn-xsrf', 'foo'); + } + + async function createConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { + log.debug('creating configuration', config.service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .put(`/api/apm/settings/agent-configuration`) + .send(config) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { + log.debug('updating configuration', config.service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .put(`/api/apm/settings/agent-configuration?overwrite=true`) + .send(config) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function deleteConfiguration( + { service }: AgentConfigurationIntake, + { user = 'write' } = {} + ) { + log.debug('deleting configuration', service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .delete(`/api/apm/settings/agent-configuration`) + .send({ service }) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + function throwOnError(res: any) { + const { statusCode, req, body } = res; + if (statusCode !== 200) { + const e = new Error(` + Endpoint: ${req.method} ${req.path} + Service: ${JSON.stringify(res.request._data.service)} + Status code: ${statusCode} + Response: ${body.message}`); + + // @ts-ignore + e.res = res; + + throw e; + } + } + + registry.when( + 'agent configuration when no data is loaded', + { config: 'basic', archives: [] }, + () => { + it('handles the empty state for services', async () => { + const { body } = await getServices(); + expect(body).to.eql(['ALL_OPTION_VALUE']); + }); + + it('handles the empty state for environments', async () => { + const { body } = await getEnvironments('myservice'); + expect(body).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); + }); + + it('handles the empty state for agent names', async () => { + const { body } = await getAgentName('myservice'); + expect(body).to.eql({}); + }); + + describe('as a read-only user', () => { + const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; + it('throws when attempting to create config', async () => { + try { + await createConfiguration(newConfig, { user: 'read' }); + + // ensure that `createConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + + describe('when a configuration already exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('throws when attempting to update config', async () => { + try { + await updateConfiguration(newConfig, { user: 'read' }); + + // ensure that `updateConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + + it('throws when attempting to delete config', async () => { + try { + await deleteConfiguration(newConfig, { user: 'read' }); + + // ensure that `deleteConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + }); + }); + + describe('when creating one configuration', () => { + const newConfig = { + service: {}, + settings: { transaction_sample_rate: '0.55' }, + }; + + const searchParams = { + service: { name: 'myservice', environment: 'development' }, + etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39', + }; + + it('can create and delete config', async () => { + // assert that config does not exist + const res1 = await searchConfigurations(searchParams); + expect(res1.status).to.equal(404); + + // assert that config was created + await createConfiguration(newConfig); + const res2 = await searchConfigurations(searchParams); + expect(res2.status).to.equal(200); + + // assert that config was deleted + await deleteConfiguration(newConfig); + const res3 = await searchConfigurations(searchParams); + expect(res3.status).to.equal(404); + }); + + describe('when a configuration exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('can find the config', async () => { + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); + expect(body._source.service).to.eql({}); + expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); + }); + + it('can list the config', async () => { + const { status, body } = await getAllConfigurations(); + expect(status).to.equal(200); + expect(omitTimestamp(body)).to.eql([ + { + service: {}, + settings: { transaction_sample_rate: '0.55' }, + applied_by_agent: false, + etag: 'eb88a8997666cc4b33745ef355a1bbd7c4782f2d', + }, + ]); + }); + + it('can update the config', async () => { + await updateConfiguration({ + service: {}, + settings: { transaction_sample_rate: '0.85' }, + }); + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); + expect(body._source.service).to.eql({}); + expect(body._source.settings).to.eql({ transaction_sample_rate: '0.85' }); + }); + }); + }); + + describe('when creating multiple configurations', () => { + const configs = [ + { + service: {}, + settings: { transaction_sample_rate: '0.1' }, + }, + { + service: { name: 'my_service' }, + settings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'development' }, + settings: { transaction_sample_rate: '0.3' }, + }, + { + service: { environment: 'production' }, + settings: { transaction_sample_rate: '0.4' }, + }, + { + service: { environment: 'development' }, + settings: { transaction_sample_rate: '0.5' }, + }, + ]; + + before(async () => { + await Promise.all(configs.map((config) => createConfiguration(config))); + }); + + after(async () => { + await Promise.all(configs.map((config) => deleteConfiguration(config))); + }); + + const agentsRequests = [ + { + service: { name: 'non_existing_service', environment: 'non_existing_env' }, + expectedSettings: { transaction_sample_rate: '0.1' }, + }, + { + service: { name: 'my_service', environment: 'non_existing_env' }, + expectedSettings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'production' }, + expectedSettings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'development' }, + expectedSettings: { transaction_sample_rate: '0.3' }, + }, + { + service: { name: 'non_existing_service', environment: 'production' }, + expectedSettings: { transaction_sample_rate: '0.4' }, + }, + { + service: { name: 'non_existing_service', environment: 'development' }, + expectedSettings: { transaction_sample_rate: '0.5' }, + }, + ]; + + it('can list all configs', async () => { + const { status, body } = await getAllConfigurations(); + expect(status).to.equal(200); + expect(orderBy(omitTimestamp(body), ['settings.transaction_sample_rate'])).to.eql([ + { + service: {}, + settings: { transaction_sample_rate: '0.1' }, + applied_by_agent: false, + etag: '0758cb18817de60cca29e07480d472694239c4c3', + }, + { + service: { name: 'my_service' }, + settings: { transaction_sample_rate: '0.2' }, + applied_by_agent: false, + etag: 'e04737637056fdf1763bf0ef0d3fcb86e89ae5fc', + }, + { + service: { name: 'my_service', environment: 'development' }, + settings: { transaction_sample_rate: '0.3' }, + applied_by_agent: false, + etag: 'af4dac62621b6762e6281481d1f7523af1124120', + }, + { + service: { environment: 'production' }, + settings: { transaction_sample_rate: '0.4' }, + applied_by_agent: false, + etag: '8d1bf8e6b778b60af351117e2cf53fb1ee570068', + }, + { + service: { environment: 'development' }, + settings: { transaction_sample_rate: '0.5' }, + applied_by_agent: false, + etag: '4ce40da57e3c71daca704121c784b911ec05ae81', + }, + ]); + }); + + for (const agentRequest of agentsRequests) { + it(`${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { + const { status, body } = await searchConfigurations({ + service: agentRequest.service, + etag: 'abc', + }); + + expect(status).to.equal(200); + expect(body._source.settings).to.eql(agentRequest.expectedSettings); + }); + } + }); + + describe('when an agent retrieves a configuration', () => { + const config = { + service: { name: 'myservice', environment: 'development' }, + settings: { transaction_sample_rate: '0.9' }, + }; + const configProduction = { + service: { name: 'myservice', environment: 'production' }, + settings: { transaction_sample_rate: '0.9' }, + }; + let etag: string; + + before(async () => { + log.debug('creating agent configuration'); + await createConfiguration(config); + await createConfiguration(configProduction); + }); + + after(async () => { + await deleteConfiguration(config); + await deleteConfiguration(configProduction); + }); + + it(`should have 'applied_by_agent=false' before supplying etag`, async () => { + const res1 = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + }); + + etag = res1.body._source.etag; + + const res2 = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + etag, + }); + + expect(res1.body._source.applied_by_agent).to.be(false); + expect(res2.body._source.applied_by_agent).to.be(false); + }); + + it(`should have 'applied_by_agent=true' after supplying etag`, async () => { + await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + etag, + }); + + async function hasBeenAppliedByAgent() { + const { body } = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + }); + + return body._source.applied_by_agent; + } + + // wait until `applied_by_agent` has been updated in elasticsearch + expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); + }); + it(`should have 'applied_by_agent=false' before marking as applied`, async () => { + const res1 = await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + }); + + expect(res1.body._source.applied_by_agent).to.be(false); + }); + it(`should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { + await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + mark_as_applied_by_agent: true, + }); + + async function hasBeenAppliedByAgent() { + const { body } = await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + }); + + return body._source.applied_by_agent; + } + + // wait until `applied_by_agent` has been updated in elasticsearch + expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); + }); + }); + } + ); + + registry.when( + 'agent configuration when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns all services', async () => { + const { body } = await getServices(); + expectSnapshot(body).toMatchInline(` + Array [ + "ALL_OPTION_VALUE", + "kibana", + "kibana-frontend", + "opbeans-dotnet", + "opbeans-go", + "opbeans-java", + "opbeans-node", + "opbeans-python", + "opbeans-ruby", + "opbeans-rum", + ] + `); + }); + + it('returns the environments, all unconfigured', async () => { + const { body } = await getEnvironments('opbeans-node'); + + expect(body.map((item: { name: string }) => item.name)).to.contain('ALL_OPTION_VALUE'); + + expect( + body.every((item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false) + ).to.be(true); + + expectSnapshot(body).toMatchInline(` + Array [ + Object { + "alreadyConfigured": false, + "name": "ALL_OPTION_VALUE", + }, + Object { + "alreadyConfigured": false, + "name": "testing", + }, + ] + `); + }); + + it('returns the agent names', async () => { + const { body } = await getAgentName('opbeans-node'); + expect(body).to.eql({ agentName: 'nodejs' }); + }); + } + ); +} + +async function waitFor(cb: () => Promise, retries = 50): Promise { + if (retries === 0) { + throw new Error(`Maximum number of retries reached`); + } + + const res = await cb(); + if (!res) { + await new Promise((resolve) => setTimeout(resolve, 100)); + return waitFor(cb, retries - 1); + } + return res; +} + +function omitTimestamp(configs: AgentConfigurationIntake[]) { + return configs.map((config: AgentConfigurationIntake) => omit(config, '@timestamp')); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts new file mode 100644 index 0000000000000..269375ef080de --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts @@ -0,0 +1,53 @@ +/* + * 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 { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const noAccessUser = getService('supertestAsNoAccessUser'); + const readUser = getService('supertestAsApmReadUser'); + const writeUser = getService('supertestAsApmWriteUser'); + + type SupertestAsUser = typeof noAccessUser | typeof readUser | typeof writeUser; + + function getJobs(user: SupertestAsUser) { + return user.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(user: SupertestAsUser, environments: string[]) { + return user + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + async function expectForbidden(user: SupertestAsUser) { + const { body: getJobsBody } = await getJobs(user); + expect(getJobsBody.statusCode).to.be(403); + expect(getJobsBody.error).to.be('Forbidden'); + + const { body: createJobsBody } = await createJobs(user, ['production', 'staging']); + + expect(createJobsBody.statusCode).to.be(403); + expect(getJobsBody.error).to.be('Forbidden'); + } + + registry.when('ML jobs return a 403 for', { config: 'basic', archives: [] }, () => { + it('user without access', async () => { + await expectForbidden(noAccessUser); + }); + + it('read user', async () => { + await expectForbidden(readUser); + }); + + it('write user', async () => { + await expectForbidden(writeUser); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts new file mode 100644 index 0000000000000..4878d5031f040 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const noAccessUser = getService('supertestAsNoAccessUser'); + + function getJobs() { + return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return noAccessUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user does not have read access to ML', () => { + describe('when calling the endpoint for listing jobs', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await getJobs(); + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await createJobs(['production', 'staging']); + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts new file mode 100644 index 0000000000000..a5fabe66af6f5 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts @@ -0,0 +1,46 @@ +/* + * 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 { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const apmReadUser = getService('supertestAsApmReadUser'); + + function getJobs() { + return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return apmReadUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user has read access to ML', () => { + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs(); + + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); + }); + }); + + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await createJobs(['production', 'staging']); + + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts new file mode 100644 index 0000000000000..5260d234eb9c7 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.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 expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const apmWriteUser = getService('supertestAsApmWriteUser'); + + function getJobs() { + return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return apmWriteUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + function deleteJobs(jobIds: string[]) { + return apmWriteUser.post(`/api/ml/jobs/delete_jobs`).send({ jobIds }).set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user has write access to ML', () => { + after(async () => { + const res = await getJobs(); + const jobIds = res.body.jobs.map((job: any) => job.job_id); + await deleteJobs(jobIds); + }); + + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs(); + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); + }); + }); + + describe('when calling create endpoint', () => { + it('creates two jobs', async () => { + await createJobs(['production', 'staging']); + + const { body } = await getJobs(); + expect(body.hasLegacyJobs).to.be(false); + expect(body.jobs.map((job: any) => job.environment)).to.eql(['production', 'staging']); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts new file mode 100644 index 0000000000000..3df905eabeace --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts @@ -0,0 +1,183 @@ +/* + * 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 URL from 'url'; +import expect from '@kbn/expect'; +import { CustomLink } from '../../../../plugins/apm/common/custom_link/custom_link_types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function customLinksTests({ getService }: FtrProviderContext) { + const supertestRead = getService('supertest'); + const supertestWrite = getService('supertestAsApmWriteUser'); + const log = getService('log'); + + const archiveName = 'apm_8.0.0'; + + registry.when('Custom links with a basic license', { config: 'basic', archives: [] }, () => { + it('returns a 403 forbidden', async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + const response = await supertestWrite + .post(`/api/apm/settings/custom_links`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + expect(response.status).to.be(403); + + expectSnapshot(response.body.message).toMatchInline( + `"To create custom links, you must be subscribed to an Elastic Gold license or above. With it, you'll have the ability to create custom links to improve your workflow when analyzing your services."` + ); + }); + }); + + registry.when( + 'Custom links with a trial license and data', + { config: 'trial', archives: [archiveName] }, + () => { + before(async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + await createCustomLink(customLink); + }); + it('fetches a custom link', async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + const { label, url, filters } = body[0]; + + expect(status).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'with filters', + url: 'https://elastic.co', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + }); + }); + it('updates a custom link', async () => { + let { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + expect(status).to.equal(200); + await updateCustomLink(body[0].id, { + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + ({ status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + })); + const { label, url, filters } = body[0]; + expect(status).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + }); + it('deletes a custom link', async () => { + let { status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(status).to.equal(200); + await deleteCustomLink(body[0].id); + ({ status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + })); + expect(status).to.equal(200); + expect(body).to.eql([]); + }); + + describe('transaction', () => { + it('fetches a transaction sample', async () => { + const response = await supertestRead.get( + '/api/apm/settings/custom_links/transaction?service.name=opbeans-java' + ); + expect(response.status).to.be(200); + expect(response.body.service.name).to.eql('opbeans-java'); + }); + }); + } + ); + + function searchCustomLinks(filters?: any) { + const path = URL.format({ + pathname: `/api/apm/settings/custom_links`, + query: filters, + }); + return supertestRead.get(path).set('kbn-xsrf', 'foo'); + } + + async function createCustomLink(customLink: CustomLink) { + log.debug('creating configuration', customLink); + const res = await supertestWrite + .post(`/api/apm/settings/custom_links`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function updateCustomLink(id: string, customLink: CustomLink) { + log.debug('updating configuration', id, customLink); + const res = await supertestWrite + .put(`/api/apm/settings/custom_links/${id}`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function deleteCustomLink(id: string) { + log.debug('deleting configuration', id); + const res = await supertestWrite + .delete(`/api/apm/settings/custom_links/${id}`) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + function throwOnError(res: any) { + const { statusCode, req, body } = res; + if (statusCode !== 200) { + throw new Error(` + Endpoint: ${req.method} ${req.path} + Service: ${JSON.stringify(res.request._data.service)} + Status code: ${statusCode} + Response: ${body.message}`); + } + } +} diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap similarity index 99% rename from x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap rename to x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap index 56e82d752dccd..c8104b9858027 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap +++ b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Traces Top traces when data is loaded returns the correct buckets 1`] = ` +exports[`APM API tests basic apm_8.0.0 Top traces when data is loaded returns the correct buckets 1`] = ` Array [ Object { "averageResponseTime": 1733, diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts similarity index 81% rename from x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts rename to x-pack/test/apm_api_integration/tests/traces/top_traces.ts index 2ce3ba3838292..4754b3faa20ad 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts @@ -5,12 +5,12 @@ */ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -20,28 +20,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = encodeURIComponent(metadata.end); const uiFilters = encodeURIComponent(JSON.stringify({})); - describe('Top traces', () => { - describe('when data is not loaded ', () => { - it('handles empty state', async () => { - const response = await supertest.get( - `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); + registry.when('Top traces when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles empty state', async () => { + const response = await supertest.get( + `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); - expect(response.status).to.be(200); - expect(response.body.items.length).to.be(0); - expect(response.body.isAggregationAccurate).to.be(true); - }); + expect(response.status).to.be(200); + expect(response.body.items.length).to.be(0); + expect(response.body.isAggregationAccurate).to.be(true); }); + }); - describe('when data is loaded', () => { + registry.when( + 'Top traces when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get( `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` ); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct status code', async () => { expect(response.status).to.be(200); @@ -116,6 +116,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap similarity index 98% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap index 25aa68d2a86b1..cfdd27b594956 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Breakdown when data is loaded returns the transaction breakdown for a service 1`] = ` +exports[`APM API tests basic apm_8.0.0 when data is loaded returns the transaction breakdown for a service 1`] = ` Object { "timeseries": Array [ Object { @@ -1019,7 +1019,7 @@ Object { } `; -exports[`APM specs (basic) Transactions Breakdown when data is loaded returns the transaction breakdown for a transaction group 9`] = ` +exports[`APM API tests basic apm_8.0.0 when data is loaded returns the transaction breakdown for a transaction group 9`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap similarity index 95% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap index 3b67a86ba84e8..d97d39cda1b8d 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Error rate when data is loaded returns the transaction error rate has the correct error rate 1`] = ` +exports[`APM API tests basic apm_8.0.0 Error rate when data is loaded returns the transaction error rate has the correct error rate 1`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap new file mode 100644 index 0000000000000..a384ca2c9364e --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environments seleted should return the correct anomaly boundaries 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 0, + "y0": 0, + }, + Object { + "x": 1607436900000, + "y": 0, + "y0": 0, + }, +] +`; + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 1625128.56211579, + "y0": 7533.02707532227, + }, + Object { + "x": 1607436900000, + "y": 1660982.24115757, + "y0": 5732.00699123528, + }, +] +`; + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 1625128.56211579, + "y0": 7533.02707532227, + }, + Object { + "x": 1607436900000, + "y": 1660982.24115757, + "y0": 5732.00699123528, + }, +] +`; diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap similarity index 96% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap index 473305f3e39af..67a02e416de51 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` +exports[`APM API tests basic apm_8.0.0 Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` Array [ Object { "averageResponseTime": 2722.75, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/transaction_charts.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.snap similarity index 100% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/transaction_charts.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.snap diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/transactions_charts.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.snap similarity index 100% rename from x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/transactions_charts.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.snap diff --git a/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts new file mode 100644 index 0000000000000..4a9dcf9273003 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts @@ -0,0 +1,118 @@ +/* + * 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 archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const metadata = archives_metadata[archiveName]; + + const start = encodeURIComponent(metadata.start); + const end = encodeURIComponent(metadata.end); + const transactionType = 'request'; + const transactionName = 'GET /api'; + const uiFilters = encodeURIComponent(JSON.stringify({})); + + registry.when('Breakdown when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + expect(response.status).to.be(200); + expect(response.body).to.eql({ timeseries: [] }); + }); + }); + + registry.when('when data is loaded', { config: 'basic', archives: [archiveName] }, () => { + it('returns the transaction breakdown for a service', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + it('returns the transaction breakdown for a transaction group', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}&transactionName=${transactionName}` + ); + + expect(response.status).to.be(200); + + const { timeseries } = response.body; + + const numberOfSeries = timeseries.length; + + expectSnapshot(numberOfSeries).toMatchInline(`1`); + + const { title, color, type, data, hideLegend, legendValue } = timeseries[0]; + + const nonNullDataPoints = data.filter((y: number | null) => y !== null); + + expectSnapshot(nonNullDataPoints.length).toMatchInline(`61`); + + expectSnapshot( + data.slice(0, 5).map(({ x, y }: { x: number; y: number | null }) => { + return { + x: new Date(x ?? NaN).toISOString(), + y, + }; + }) + ).toMatchInline(` + Array [ + Object { + "x": "2020-12-08T13:57:30.000Z", + "y": null, + }, + Object { + "x": "2020-12-08T13:58:00.000Z", + "y": null, + }, + Object { + "x": "2020-12-08T13:58:30.000Z", + "y": 1, + }, + Object { + "x": "2020-12-08T13:59:00.000Z", + "y": 1, + }, + Object { + "x": "2020-12-08T13:59:30.000Z", + "y": null, + }, + ] + `); + + expectSnapshot(title).toMatchInline(`"app"`); + expectSnapshot(color).toMatchInline(`"#54b399"`); + expectSnapshot(type).toMatchInline(`"areaStacked"`); + expectSnapshot(hideLegend).toMatchInline(`false`); + expectSnapshot(legendValue).toMatchInline(`"100%"`); + + expectSnapshot(data).toMatch(); + }); + it('returns the transaction breakdown sorted by name', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body.timeseries.map((serie: { title: string }) => serie.title)) + .toMatchInline(` + Array [ + "app", + "http", + "postgresql", + "redis", + ] + `); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts similarity index 85% rename from x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts rename to x-pack/test/apm_api_integration/tests/transactions/distribution.ts index 890b6af728d5a..5d918b479111f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts @@ -6,12 +6,12 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { isEmpty } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -24,8 +24,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { transactionType: 'request', })}`; - describe('Transaction groups distribution', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Transaction groups distribution when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get(url); @@ -34,15 +36,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.noHits).to.be(true); expect(response.body.buckets.length).to.be(0); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Transaction groups distribution when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get(url); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct metadata', () => { expect(response.status).to.be(200); @@ -91,6 +95,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts similarity index 71% rename from x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts rename to x-pack/test/apm_api_integration/tests/transactions/error_rate.ts index 22d9a7eba7fbf..487f2efbae400 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts @@ -6,12 +6,12 @@ import expect from '@kbn/expect'; import { first, last } from 'lodash'; import { format } from 'url'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; @@ -20,28 +20,27 @@ export default function ApiTest({ getService }: FtrProviderContext) { const uiFilters = '{}'; const transactionType = 'request'; - describe('Error rate', () => { - const url = format({ - pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', - query: { start, end, uiFilters, transactionType }, - }); + const url = format({ + pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', + query: { start, end, uiFilters, transactionType }, + }); - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - expect(response.status).to.be(200); + registry.when('Error rate when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + expect(response.status).to.be(200); - expect(response.body.noHits).to.be(true); + expect(response.body.noHits).to.be(true); - expect(response.body.transactionErrorRate.length).to.be(0); - expect(response.body.average).to.be(null); - }); + expect(response.body.transactionErrorRate.length).to.be(0); + expect(response.body.average).to.be(null); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Error rate when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { describe('returns the transaction error rate', () => { let errorRateResponse: { transactionErrorRate: Array<{ x: number; y: number | null }>; @@ -89,6 +88,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(errorRateResponse.transactionErrorRate).toMatch(); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts new file mode 100644 index 0000000000000..c860b0e75495c --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -0,0 +1,243 @@ +/* + * 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 { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + + const range = archives_metadata[archiveName]; + + // url parameters + const start = encodeURIComponent(range.start); + const end = encodeURIComponent(range.end); + + registry.when( + 'Latency with a basic license when data is not loaded ', + { config: 'basic', archives: [] }, + () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + it('returns 400 when latencyAggregationType is not informed', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` + ); + + expect(response.status).to.be(400); + }); + + it('returns 400 when transactionType is not informed', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + + expect(response.status).to.be(400); + }); + + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` + ); + + expect(response.status).to.be(200); + + expect(response.body.overallAvgDuration).to.be(null); + expect(response.body.latencyTimeseries.length).to.be(0); + }); + } + ); + + registry.when( + 'Latency with a basic license when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let response: PromiseReturnType; + + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + + describe('average latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + + describe('95th percentile latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + + describe('99th percentile latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + } + ); + + registry.when( + 'Transaction latency with a trial license when data is loaded', + { config: 'trial', archives: [archiveName] }, + () => { + let response: PromiseReturnType; + + const transactionType = 'request'; + + describe('without environment', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + it('should return an error response', () => { + expect(response.status).to.eql(400); + }); + }); + + describe('without uiFilters', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + ); + }); + it('should return an error response', () => { + expect(response.status).to.eql(400); + }); + }); + + describe('with environment selected in uiFilters', () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'production' })); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies of the selected environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-production-1369-high_mean_transaction_duration"` + ); + }); + + it('should return a non-empty anomaly series', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + + describe('when not defined environments seleted', () => { + const uiFilters = encodeURIComponent( + JSON.stringify({ environment: 'ENVIRONMENT_NOT_DEFINED' }) + ); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-python/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies with no defined environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-environment_not_defined-5626-high_mean_transaction_duration"` + ); + }); + + it('should return the correct anomaly boundaries', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + + describe('with all environments selected', () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'ENVIRONMENT_ALL' })); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should not return anomaly timeseries data', () => { + expect(response.body).to.not.have.property('anomalyTimeseries'); + }); + }); + + describe('with environment selected and empty kuery filter', () => { + const uiFilters = encodeURIComponent( + JSON.stringify({ kuery: '', environment: 'production' }) + ); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies of the selected environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-production-1369-high_mean_transaction_duration"` + ); + }); + + it('should return a non-empty anomaly series', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts similarity index 54% rename from x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts rename to x-pack/test/apm_api_integration/tests/transactions/throughput.ts index 1013f3a19d71f..475bef4e9b549 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts @@ -5,13 +5,13 @@ */ import expect from '@kbn/expect'; import url from 'url'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -20,31 +20,30 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { start, end } = metadata; const uiFilters = JSON.stringify({ environment: 'testing' }); - describe('Throughput', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, - query: { - start, - end, - uiFilters, - transactionType: 'request', - }, - }) - ); + registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, + query: { + start, + end, + uiFilters, + transactionType: 'request', + }, + }) + ); - expect(response.status).to.be(200); + expect(response.status).to.be(200); - expect(response.body.throughputTimeseries.length).to.be(0); - }); + expect(response.body.throughputTimeseries.length).to.be(0); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Throughput when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: PromiseReturnType; before(async () => { @@ -66,6 +65,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.throughputTimeseries.length).to.be.greaterThan(0); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts b/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts similarity index 80% rename from x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts rename to x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts index dac36ae8b3303..70afb2ee384c4 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; function sortTransactionGroups(items: any[]) { return sortBy(items, 'impact'); @@ -14,7 +15,6 @@ function sortTransactionGroups(items: any[]) { export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -25,8 +25,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const uiFilters = encodeURIComponent(JSON.stringify({})); const transactionType = 'request'; - describe('Top transaction groups', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Top transaction groups when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transactions/groups?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` @@ -37,17 +39,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.isAggregationAccurate).to.be(true); expect(response.body.items.length).to.be(0); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Top transaction groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get( `/api/apm/services/opbeans-node/transactions/groups?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` ); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct metadata', () => { expect(response.status).to.be(200); @@ -62,6 +66,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct buckets (when ignoring samples)', async () => { expectSnapshot(sortTransactionGroups(response.body.items)).toMatch(); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts similarity index 94% rename from x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts rename to x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts index be978b2a82618..8818aaccdec01 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts @@ -7,18 +7,20 @@ import expect from '@kbn/expect'; import { pick, uniqBy, sortBy } from 'lodash'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Transactions groups overview', () => { - describe('when data is not loaded', () => { + registry.when( + 'Transaction groups overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -45,12 +47,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { isAggregationAccurate: true, }); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Top transaction groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the correct data', async () => { const response = await supertest.get( url.format({ @@ -264,6 +267,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(uniqBy(items, 'name').length).to.eql(totalItems); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/config.ts b/x-pack/test/apm_api_integration/trial/config.ts index 94a6f808603c1..ba9ef4ca42597 100644 --- a/x-pack/test/apm_api_integration/trial/config.ts +++ b/x-pack/test/apm_api_integration/trial/config.ts @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createTestConfig } from '../common/config'; +import { configs } from '../configs'; -export default createTestConfig({ - license: 'trial', - name: 'X-Pack APM API integration tests (trial)', - testFiles: [require.resolve('./tests')], -}); +export default configs.trial; diff --git a/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts b/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts deleted file mode 100644 index 9a868373292f6..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts +++ /dev/null @@ -1,95 +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 { format } from 'url'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; - const range = archives_metadata[archiveName]; - - describe('Slow durations', () => { - const url = format({ - pathname: `/api/apm/correlations/slow_transactions`, - query: { - start: range.start, - end: range.end, - durationPercentile: 95, - fieldNames: - 'user.username,user.id,host.ip,user_agent.name,kubernetes.pod.uuid,url.domain,container.id,service.node.name', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body.response).to.be(undefined); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('making request with default args', () => { - type ResponseBody = APIReturnType<'GET /api/apm/correlations/slow_transactions'>; - let response: { - status: number; - body: NonNullable; - }; - - before(async () => { - response = await supertest.get(url); - }); - - it('returns successfully', () => { - expect(response.status).to.eql(200); - }); - - it('returns significant terms', () => { - const sorted = response.body?.significantTerms?.sort(); - expectSnapshot(sorted?.map((term) => term.fieldName)).toMatchInline(` - Array [ - "user_agent.name", - "url.domain", - "host.ip", - "service.node.name", - "container.id", - "url.domain", - "user_agent.name", - ] - `); - }); - - it('returns a distribution per term', () => { - expectSnapshot(response.body?.significantTerms?.map((term) => term.distribution.length)) - .toMatchInline(` - Array [ - 11, - 11, - 11, - 11, - 11, - 11, - 11, - ] - `); - }); - - it('returns overall distribution', () => { - expectSnapshot(response.body?.overall?.distribution.length).toMatchInline(`11`); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts deleted file mode 100644 index 05c6439508ece..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts +++ /dev/null @@ -1,47 +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 { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM Services', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns rum services list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(`Array []`); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts deleted file mode 100644 index fa5fcbcb6a7c3..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('UX page load dist', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns empty list with breakdowns', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdown=Browser' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns page load distribution', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - it('returns page load distribution with breakdown', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdown=Browser' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts deleted file mode 100644 index 5d910862843d5..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM page views', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns empty list with breakdowns', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns page views', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - it('returns page views with breakdown', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts deleted file mode 100644 index 961c783434639..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM url search api', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [], - "total": 0, - } - `); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns top urls when no query', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [ - Object { - "count": 5, - "pld": 4924000, - "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", - }, - Object { - "count": 1, - "pld": 2760000, - "url": "http://localhost:5601/nfw/app/home", - }, - ], - "total": 2, - } - `); - }); - - it('returns specific results against query', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&urlQuery=csm&percentile=50' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [ - Object { - "count": 5, - "pld": 4924000, - "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", - }, - ], - "total": 1, - } - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts deleted file mode 100644 index c8ee858d9ceb0..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ /dev/null @@ -1,50 +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 { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; - -export default function observabilityApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('APM specs (trial)', function () { - this.tags('ciGroup1'); - - describe('Services', function () { - loadTestFile(require.resolve('./services/annotations')); - loadTestFile(require.resolve('./services/top_services.ts')); - }); - - describe('Transactions', function () { - loadTestFile(require.resolve('./transactions/latency')); - }); - - describe('Settings', function () { - loadTestFile(require.resolve('./settings/custom_link.ts')); - describe('Anomaly detection', function () { - loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); - }); - }); - - describe('Service Maps', function () { - loadTestFile(require.resolve('./service_maps/service_maps')); - }); - - describe('CSM', function () { - loadTestFile(require.resolve('./csm/csm_services.ts')); - loadTestFile(require.resolve('./csm/web_core_vitals.ts')); - loadTestFile(require.resolve('./csm/long_task_metrics.ts')); - loadTestFile(require.resolve('./csm/url_search.ts')); - loadTestFile(require.resolve('./csm/page_views.ts')); - loadTestFile(require.resolve('./csm/js_errors.ts')); - loadTestFile(require.resolve('./csm/has_rum_data.ts')); - loadTestFile(require.resolve('./csm/page_load_dist.ts')); - }); - - describe('Correlations', function () { - loadTestFile(require.resolve('./correlations/slow_transactions')); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts deleted file mode 100644 index e37d98b41b7af..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts +++ /dev/null @@ -1,131 +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 { sortBy } from 'lodash'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('APM Services Overview', () => { - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('with the default APM read user', () => { - describe('and fetching a list of services', () => { - let response: { - status: number; - body: APIReturnType<'GET /api/apm/services'>; - }; - - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('there is at least one service', () => { - expect(response.body.items.length).to.be.greaterThan(0); - }); - - it('some items have a health status set', () => { - // Under the assumption that the loaded archive has - // at least one APM ML job, and the time range is longer - // than 15m, at least one items should have a health status - // set. Note that we currently have a bug where healthy - // services report as unknown (so without any health status): - // https://github.com/elastic/kibana/issues/77083 - - const healthStatuses = sortBy(response.body.items, 'serviceName').map( - (item: any) => item.healthStatus - ); - - expect(healthStatuses.filter(Boolean).length).to.be.greaterThan(0); - - expectSnapshot(healthStatuses).toMatchInline(` - Array [ - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - ] - `); - }); - }); - }); - - describe('with a user that does not have access to ML', () => { - let response: PromiseReturnType; - before(async () => { - response = await supertestAsApmReadUserWithoutMlAccess.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('there is at least one service', () => { - expect(response.body.items.length).to.be.greaterThan(0); - }); - - it('contains no health statuses', () => { - const definedHealthStatuses = response.body.items - .map((item: any) => item.healthStatus) - .filter(Boolean); - - expect(definedHealthStatuses.length).to.be(0); - }); - }); - - describe('and fetching a list of services with a filter', () => { - let response: PromiseReturnType; - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${encodeURIComponent( - `{"kuery":"service.name:opbeans-java","environment":"ENVIRONMENT_ALL"}` - )}` - ); - }); - - it('does not return health statuses for services that are not found in APM data', () => { - expect(response.status).to.be(200); - - expect(response.body.items.length).to.be(1); - - expect(response.body.items[0].serviceName).to.be('opbeans-java'); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts deleted file mode 100644 index a917bdb3cea23..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts +++ /dev/null @@ -1,41 +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 { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const noAccessUser = getService('supertestAsNoAccessUser'); - - function getJobs() { - return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return noAccessUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user does not have read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await getJobs(); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createJobs(['production', 'staging']); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts deleted file mode 100644 index 2265c4dc0a41d..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.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 { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmReadUser = getService('supertestAsApmReadUser'); - - function getJobs() { - return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return apmReadUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs(); - - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts deleted file mode 100644 index 720d66e1efcc8..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmWriteUser = getService('supertestAsApmWriteUser'); - - function getJobs() { - return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return apmWriteUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - function deleteJobs(jobIds: string[]) { - return apmWriteUser.post(`/api/ml/jobs/delete_jobs`).send({ jobIds }).set('kbn-xsrf', 'foo'); - } - - describe('when user has write access to ML', () => { - after(async () => { - const res = await getJobs(); - const jobIds = res.body.jobs.map((job: any) => job.job_id); - await deleteJobs(jobIds); - }); - - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs(); - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); - }); - - describe('when calling create endpoint', () => { - it('creates two jobs', async () => { - await createJobs(['production', 'staging']); - - const { body } = await getJobs(); - expect(body.hasLegacyJobs).to.be(false); - expect(body.jobs.map((job: any) => job.environment)).to.eql(['production', 'staging']); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts deleted file mode 100644 index bcfe8fce4b948..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts +++ /dev/null @@ -1,159 +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 URL from 'url'; -import expect from '@kbn/expect'; -import { CustomLink } from '../../../../../plugins/apm/common/custom_link/custom_link_types'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertest'); - const supertestWrite = getService('supertestAsApmWriteUser'); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - function searchCustomLinks(filters?: any) { - const path = URL.format({ - pathname: `/api/apm/settings/custom_links`, - query: filters, - }); - return supertestRead.get(path).set('kbn-xsrf', 'foo'); - } - - async function createCustomLink(customLink: CustomLink) { - log.debug('creating configuration', customLink); - const res = await supertestWrite - .post(`/api/apm/settings/custom_links`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function updateCustomLink(id: string, customLink: CustomLink) { - log.debug('updating configuration', id, customLink); - const res = await supertestWrite - .put(`/api/apm/settings/custom_links/${id}`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function deleteCustomLink(id: string) { - log.debug('deleting configuration', id); - const res = await supertestWrite - .delete(`/api/apm/settings/custom_links/${id}`) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - function throwOnError(res: any) { - const { statusCode, req, body } = res; - if (statusCode !== 200) { - throw new Error(` - Endpoint: ${req.method} ${req.path} - Service: ${JSON.stringify(res.request._data.service)} - Status code: ${statusCode} - Response: ${body.message}`); - } - } - - describe('custom links', () => { - before(async () => { - const customLink = { - url: 'https://elastic.co', - label: 'with filters', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - } as CustomLink; - await createCustomLink(customLink); - }); - it('fetches a custom link', async () => { - const { status, body } = await searchCustomLinks({ - 'service.name': 'baz', - 'transaction.type': 'qux', - }); - const { label, url, filters } = body[0]; - - expect(status).to.equal(200); - expect({ label, url, filters }).to.eql({ - label: 'with filters', - url: 'https://elastic.co', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - }); - }); - it('updates a custom link', async () => { - let { status, body } = await searchCustomLinks({ - 'service.name': 'baz', - 'transaction.type': 'qux', - }); - expect(status).to.equal(200); - await updateCustomLink(body[0].id, { - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', - filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, - ], - }); - ({ status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - })); - const { label, url, filters } = body[0]; - expect(status).to.equal(200); - expect({ label, url, filters }).to.eql({ - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', - filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, - ], - }); - }); - it('deletes a custom link', async () => { - let { status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); - expect(status).to.equal(200); - await deleteCustomLink(body[0].id); - ({ status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - })); - expect(status).to.equal(200); - expect(body).to.eql([]); - }); - - describe('transaction', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('fetches a transaction sample', async () => { - const response = await supertestRead.get( - '/api/apm/settings/custom_links/transaction?service.name=opbeans-java' - ); - expect(response.status).to.be(200); - expect(response.body.service.name).to.eql('opbeans-java'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap deleted file mode 100644 index 9475670387a08..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters when not defined environments seleted should return the correct anomaly boundaries 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 0, - "y0": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - "y0": 0, - }, -] -`; - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, - }, - Object { - "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, -] -`; - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, - }, - Object { - "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, -] -`; diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts deleted file mode 100644 index e0b9559be7208..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts +++ /dev/null @@ -1,159 +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 { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - const transactionType = 'request'; - - describe('Latency', () => { - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('and fetching transaction charts with uiFilters', () => { - let response: PromiseReturnType; - - describe('without environment', () => { - const uiFilters = encodeURIComponent(JSON.stringify({})); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - it('should return an error response', () => { - expect(response.status).to.eql(400); - }); - }); - - describe('without uiFilters', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` - ); - }); - it('should return an error response', () => { - expect(response.status).to.eql(400); - }); - }); - - describe('with environment selected in uiFilters', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'production' })); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-production-1369-high_mean_transaction_duration"` - ); - }); - - it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - - describe('when not defined environments seleted', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ environment: 'ENVIRONMENT_NOT_DEFINED' }) - ); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-python/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies with no defined environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-environment_not_defined-5626-high_mean_transaction_duration"` - ); - }); - - it('should return the correct anomaly boundaries', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - - describe('with all environments selected', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'ENVIRONMENT_ALL' })); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should not return anomaly timeseries data', () => { - expect(response.body).to.not.have.property('anomalyTimeseries'); - }); - }); - - describe('with environment selected and empty kuery filter', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ kuery: '', environment: 'production' }) - ); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-production-1369-high_mean_transaction_duration"` - ); - }); - - it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - }); - }); - }); -} From 64e9cf0440e1e60b25a8b04939cb9d0097efcb53 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 27 Jan 2021 12:45:49 +0200 Subject: [PATCH 15/16] Cleanup OSS code from visualizations wizard (#89092) * Cleanup OSS code from visualizations wizard * Remove unecessary translations * Remove unused translation * Fix functional test * Disable the functional test for OSS * Remove from oss correctly :D * Fix ci --- .github/CODEOWNERS | 2 - .i18nrc.json | 2 - docs/developer/plugin-list.asciidoc | 10 --- .../deprecation/deprecation_factory.test.ts | 85 +------------------ .../src/deprecation/deprecation_factory.ts | 26 ------ packages/kbn-config/src/deprecation/index.ts | 2 +- packages/kbn-config/src/index.ts | 1 - packages/kbn-optimizer/limits.yml | 2 - src/plugins/lens_oss/README.md | 6 -- src/plugins/lens_oss/common/constants.ts | 12 --- src/plugins/lens_oss/common/index.ts | 9 -- src/plugins/lens_oss/config.ts | 15 ---- src/plugins/lens_oss/kibana.json | 10 --- src/plugins/lens_oss/public/index.ts | 11 --- src/plugins/lens_oss/public/plugin.ts | 32 ------- src/plugins/lens_oss/public/vis_type_alias.ts | 37 -------- src/plugins/lens_oss/server/index.ts | 21 ----- src/plugins/lens_oss/tsconfig.json | 20 ----- src/plugins/maps_oss/README.md | 6 -- src/plugins/maps_oss/common/constants.ts | 12 --- src/plugins/maps_oss/common/index.ts | 9 -- src/plugins/maps_oss/config.ts | 15 ---- src/plugins/maps_oss/kibana.json | 10 --- src/plugins/maps_oss/public/index.ts | 11 --- src/plugins/maps_oss/public/plugin.ts | 32 ------- src/plugins/maps_oss/public/vis_type_alias.ts | 33 ------- src/plugins/maps_oss/server/index.ts | 21 ----- .../vis_types/vis_type_alias_registry.ts | 7 -- .../group_selection/group_selection.test.tsx | 40 --------- .../group_selection/group_selection.tsx | 26 ------ .../functional/apps/visualize/_chart_types.ts | 18 +--- test/functional/apps/visualize/index.ts | 6 +- tsconfig.json | 2 - tsconfig.refs.json | 1 - x-pack/plugins/lens/kibana.json | 2 +- x-pack/plugins/lens/public/plugin.ts | 3 - x-pack/plugins/lens/tsconfig.json | 1 - x-pack/plugins/maps/kibana.json | 2 +- x-pack/plugins/maps/public/plugin.ts | 4 - .../translations/translations/ja-JP.json | 8 -- .../translations/translations/zh-CN.json | 8 -- 41 files changed, 10 insertions(+), 570 deletions(-) delete mode 100644 src/plugins/lens_oss/README.md delete mode 100644 src/plugins/lens_oss/common/constants.ts delete mode 100644 src/plugins/lens_oss/common/index.ts delete mode 100644 src/plugins/lens_oss/config.ts delete mode 100644 src/plugins/lens_oss/kibana.json delete mode 100644 src/plugins/lens_oss/public/index.ts delete mode 100644 src/plugins/lens_oss/public/plugin.ts delete mode 100644 src/plugins/lens_oss/public/vis_type_alias.ts delete mode 100644 src/plugins/lens_oss/server/index.ts delete mode 100644 src/plugins/lens_oss/tsconfig.json delete mode 100644 src/plugins/maps_oss/README.md delete mode 100644 src/plugins/maps_oss/common/constants.ts delete mode 100644 src/plugins/maps_oss/common/index.ts delete mode 100644 src/plugins/maps_oss/config.ts delete mode 100644 src/plugins/maps_oss/kibana.json delete mode 100644 src/plugins/maps_oss/public/index.ts delete mode 100644 src/plugins/maps_oss/public/plugin.ts delete mode 100644 src/plugins/maps_oss/public/vis_type_alias.ts delete mode 100644 src/plugins/maps_oss/server/index.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b47d78ea6d691..0630937d5ac4b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,7 +13,6 @@ /src/plugins/advanced_settings/ @elastic/kibana-app /src/plugins/charts/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app -/src/plugins/lens_oss/ @elastic/kibana-app /src/plugins/management/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app @@ -127,7 +126,6 @@ /x-pack/test/functional/es_archives/maps/ @elastic/kibana-gis /x-pack/test/visual_regression/tests/maps/index.js @elastic/kibana-gis #CC# /src/plugins/maps_legacy/ @elastic/kibana-gis -#CC# /src/plugins/maps_oss/ @elastic/kibana-gis #CC# /x-pack/plugins/file_upload @elastic/kibana-gis #CC# /x-pack/plugins/maps_legacy_licensing @elastic/kibana-gis /src/plugins/tile_map/ @elastic/kibana-gis diff --git a/.i18nrc.json b/.i18nrc.json index b425dd99857dc..0cdcae08e54e0 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -61,8 +61,6 @@ "visTypeVislib": "src/plugins/vis_type_vislib", "visTypeXy": "src/plugins/vis_type_xy", "visualizations": "src/plugins/visualizations", - "lensOss": "src/plugins/lens_oss", - "mapsOss": "src/plugins/maps_oss", "visualize": "src/plugins/visualize", "apmOss": "src/plugins/apm_oss", "usageCollection": "src/plugins/usage_collection" diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 7ce4896a8bce4..fd4ed75352b1f 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -130,11 +130,6 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |The legacyExport plugin adds support for the legacy saved objects export format. -|{kib-repo}blob/{branch}/src/plugins/lens_oss/README.md[lensOss] -|The lens_oss plugin registers the lens visualization on OSS. -It is registered as disabled. The x-pack plugin should unregister this. - - |{kib-repo}blob/{branch}/src/plugins/management/README.md[management] |This plugins contains the "Stack Management" page framework. It offers navigation and an API to link individual managment section into it. This plugin does not contain any individual @@ -145,11 +140,6 @@ management section itself. |Internal objects used by the Coordinate, Region, and Vega visualizations. -|{kib-repo}blob/{branch}/src/plugins/maps_oss/README.md[mapsOss] -|The maps_oss plugin registers the maps visualization on OSS. -It is registered as disabled. The x-pack plugin should unregister this. - - |{kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation] |The navigation plugins exports the TopNavMenu component. It also provides a stateful version of it on the start contract. diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts index 5184494dd74bf..1c13c539c3746 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts @@ -7,7 +7,7 @@ */ import { ConfigDeprecationLogger } from './types'; -import { configDeprecationFactory, copyFromRoot } from './deprecation_factory'; +import { configDeprecationFactory } from './deprecation_factory'; describe('DeprecationFactory', () => { const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory; @@ -239,89 +239,6 @@ describe('DeprecationFactory', () => { }); }); - describe('copyFromRoot', () => { - it('copies a property to a different namespace', () => { - const rawConfig = { - originplugin: { - deprecated: 'toberenamed', - valid: 'valid', - }, - destinationplugin: { - property: 'value', - }, - }; - const processed = copyFromRoot('originplugin.deprecated', 'destinationplugin.renamed')( - rawConfig, - 'does-not-matter', - logger - ); - expect(processed).toEqual({ - originplugin: { - deprecated: 'toberenamed', - valid: 'valid', - }, - destinationplugin: { - renamed: 'toberenamed', - property: 'value', - }, - }); - expect(deprecationMessages.length).toEqual(0); - }); - - it('does not alter config if origin property is not present', () => { - const rawConfig = { - myplugin: { - new: 'new', - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, - }; - const processed = copyFromRoot('myplugin.deprecated', 'myplugin.new')( - rawConfig, - 'does-not-matter', - logger - ); - expect(processed).toEqual({ - myplugin: { - new: 'new', - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, - }); - expect(deprecationMessages.length).toEqual(0); - }); - - it('does not alter config if they both exist', () => { - const rawConfig = { - myplugin: { - deprecated: 'deprecated', - renamed: 'renamed', - }, - someOtherPlugin: { - property: 'value', - }, - }; - const processed = copyFromRoot('myplugin.deprecated', 'someOtherPlugin.property')( - rawConfig, - 'does-not-matter', - logger - ); - expect(processed).toEqual({ - myplugin: { - deprecated: 'deprecated', - renamed: 'renamed', - }, - someOtherPlugin: { - property: 'value', - }, - }); - }); - }); - describe('unused', () => { it('removes the unused property from the config and logs a warning is present', () => { const rawConfig = { diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts index 40e29f9739156..04826446dc1ad 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts @@ -45,26 +45,6 @@ const _rename = ( return config; }; -const _copy = ( - config: Record, - rootPath: string, - originKey: string, - destinationKey: string -) => { - const originPath = getPath(rootPath, originKey); - const originValue = get(config, originPath); - if (originValue === undefined) { - return config; - } - - const destinationPath = getPath(rootPath, destinationKey); - const destinationValue = get(config, destinationPath); - if (destinationValue === undefined) { - set(config, destinationPath, originValue); - } - return config; -}; - const _unused = ( config: Record, rootPath: string, @@ -89,12 +69,6 @@ const renameFromRoot = (oldKey: string, newKey: string, silent?: boolean): Confi log ) => _rename(config, '', log, oldKey, newKey, silent); -export const copyFromRoot = (originKey: string, destinationKey: string): ConfigDeprecation => ( - config, - rootPath, - log -) => _copy(config, '', originKey, destinationKey); - const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => _unused(config, rootPath, log, unusedKey); diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts index 87ec43d1fb09f..6deebd27399bb 100644 --- a/packages/kbn-config/src/deprecation/index.ts +++ b/packages/kbn-config/src/deprecation/index.ts @@ -13,5 +13,5 @@ export { ConfigDeprecationFactory, ConfigDeprecationProvider, } from './types'; -export { configDeprecationFactory, copyFromRoot } from './deprecation_factory'; +export { configDeprecationFactory } from './deprecation_factory'; export { applyDeprecations } from './apply_deprecations'; diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index e1743270f62c7..a803e3fd2dc8e 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -14,7 +14,6 @@ export { ConfigDeprecationLogger, ConfigDeprecationProvider, ConfigDeprecationWithContext, - copyFromRoot, } from './deprecation'; export { RawConfigurationProvider, RawConfigService, getConfigFromFiles } from './raw'; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index ef672d6cbeb2e..1a4fb390d0c17 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -44,7 +44,6 @@ pageLoadAssetSize: kibanaReact: 161921 kibanaUtils: 198829 lens: 96624 - lensOss: 19341 licenseManagement: 41817 licensing: 39008 lists: 202261 @@ -53,7 +52,6 @@ pageLoadAssetSize: maps: 183610 mapsLegacy: 116817 mapsLegacyLicensing: 20214 - mapsOss: 19284 ml: 82187 monitoring: 50000 navigation: 37269 diff --git a/src/plugins/lens_oss/README.md b/src/plugins/lens_oss/README.md deleted file mode 100644 index 187da2497026e..0000000000000 --- a/src/plugins/lens_oss/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# lens_oss - -The lens_oss plugin registers the lens visualization on OSS. -It is registered as disabled. The x-pack plugin should unregister this. - -`visualizations.unregisterAlias('lensOss')` \ No newline at end of file diff --git a/src/plugins/lens_oss/common/constants.ts b/src/plugins/lens_oss/common/constants.ts deleted file mode 100644 index 0ff5cdd78bb1b..0000000000000 --- a/src/plugins/lens_oss/common/constants.ts +++ /dev/null @@ -1,12 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export const APP_NAME = 'lens'; -export const PLUGIN_ID_OSS = 'lensOss'; -export const APP_PATH = '#/'; -export const APP_ICON = 'lensApp'; diff --git a/src/plugins/lens_oss/common/index.ts b/src/plugins/lens_oss/common/index.ts deleted file mode 100644 index 7f60b8508dc27..0000000000000 --- a/src/plugins/lens_oss/common/index.ts +++ /dev/null @@ -1,9 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export * from './constants'; diff --git a/src/plugins/lens_oss/config.ts b/src/plugins/lens_oss/config.ts deleted file mode 100644 index 58c50f0104f46..0000000000000 --- a/src/plugins/lens_oss/config.ts +++ /dev/null @@ -1,15 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { schema, TypeOf } from '@kbn/config-schema'; - -export const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), -}); - -export type ConfigSchema = TypeOf; diff --git a/src/plugins/lens_oss/kibana.json b/src/plugins/lens_oss/kibana.json deleted file mode 100644 index 3e3d3585f37fb..0000000000000 --- a/src/plugins/lens_oss/kibana.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "lensOss", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": [ - "visualizations" - ], - "extraPublicDirs": ["common/constants"] -} diff --git a/src/plugins/lens_oss/public/index.ts b/src/plugins/lens_oss/public/index.ts deleted file mode 100644 index 2f68f6d183a22..0000000000000 --- a/src/plugins/lens_oss/public/index.ts +++ /dev/null @@ -1,11 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { LensOSSPlugin } from './plugin'; - -export const plugin = () => new LensOSSPlugin(); diff --git a/src/plugins/lens_oss/public/plugin.ts b/src/plugins/lens_oss/public/plugin.ts deleted file mode 100644 index 5a441614b7e24..0000000000000 --- a/src/plugins/lens_oss/public/plugin.ts +++ /dev/null @@ -1,32 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { DocLinksStart, CoreSetup } from 'src/core/public'; -import { VisualizationsSetup } from '../../visualizations/public'; -import { getLensAliasConfig } from './vis_type_alias'; - -export interface LensPluginSetupDependencies { - visualizations: VisualizationsSetup; -} - -export interface LensPluginStartDependencies { - docLinks: DocLinksStart; -} - -export class LensOSSPlugin { - setup( - core: CoreSetup, - { visualizations }: LensPluginSetupDependencies - ) { - core.getStartServices().then(([coreStart]) => { - visualizations.registerAlias(getLensAliasConfig(coreStart.docLinks)); - }); - } - - start() {} -} diff --git a/src/plugins/lens_oss/public/vis_type_alias.ts b/src/plugins/lens_oss/public/vis_type_alias.ts deleted file mode 100644 index b9806bbf3b4e5..0000000000000 --- a/src/plugins/lens_oss/public/vis_type_alias.ts +++ /dev/null @@ -1,37 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { VisTypeAlias } from 'src/plugins/visualizations/public'; -import { DocLinksStart } from 'src/core/public'; -import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common'; - -export const getLensAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({ - aliasPath: APP_PATH, - aliasApp: APP_NAME, - name: PLUGIN_ID_OSS, - title: i18n.translate('lensOss.visTypeAlias.title', { - defaultMessage: 'Lens', - }), - description: i18n.translate('lensOss.visTypeAlias.description', { - defaultMessage: - 'Create visualizations with our drag-and-drop editor. Switch between visualization types at any time. Best for most visualizations.', - }), - icon: APP_ICON, - stage: 'production', - disabled: true, - note: i18n.translate('lensOss.visTypeAlias.note', { - defaultMessage: 'Recommended for most users.', - }), - promoTooltip: { - description: i18n.translate('lensOss.visTypeAlias.promoTooltip.description', { - defaultMessage: 'Try Lens for free with Elastic. Learn more.', - }), - link: `${links.visualize.lens}?blade=kibanaossvizwizard`, - }, -}); diff --git a/src/plugins/lens_oss/server/index.ts b/src/plugins/lens_oss/server/index.ts deleted file mode 100644 index d13a9b2caaeb2..0000000000000 --- a/src/plugins/lens_oss/server/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { PluginConfigDescriptor } from 'kibana/server'; -import { copyFromRoot } from '@kbn/config'; -import { configSchema, ConfigSchema } from '../config'; - -export const config: PluginConfigDescriptor = { - schema: configSchema, - deprecations: () => [copyFromRoot('xpack.lens.enabled', 'lens_oss.enabled')], -}; - -export const plugin = () => ({ - setup() {}, - start() {}, -}); diff --git a/src/plugins/lens_oss/tsconfig.json b/src/plugins/lens_oss/tsconfig.json deleted file mode 100644 index d7bbc593fa87b..0000000000000 --- a/src/plugins/lens_oss/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "composite": true, - "outDir": "./target/types", - "emitDeclarationOnly": true, - "declaration": true, - "declarationMap": true - }, - "include": [ - "common/**/*", - "public/**/*", - "server/**/*", - "*.ts" - ], - "references": [ - { "path": "../../core/tsconfig.json" }, - { "path": "../visualizations/tsconfig.json" } - ] -} diff --git a/src/plugins/maps_oss/README.md b/src/plugins/maps_oss/README.md deleted file mode 100644 index ed91de500fbfb..0000000000000 --- a/src/plugins/maps_oss/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# maps_oss - -The maps_oss plugin registers the maps visualization on OSS. -It is registered as disabled. The x-pack plugin should unregister this. - -`visualizations.unregisterAlias('mapsOss')` \ No newline at end of file diff --git a/src/plugins/maps_oss/common/constants.ts b/src/plugins/maps_oss/common/constants.ts deleted file mode 100644 index db29f541a03df..0000000000000 --- a/src/plugins/maps_oss/common/constants.ts +++ /dev/null @@ -1,12 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export const APP_NAME = 'maps'; -export const PLUGIN_ID_OSS = 'mapsOss'; -export const APP_PATH = '/map'; -export const APP_ICON = 'gisApp'; diff --git a/src/plugins/maps_oss/common/index.ts b/src/plugins/maps_oss/common/index.ts deleted file mode 100644 index 7f60b8508dc27..0000000000000 --- a/src/plugins/maps_oss/common/index.ts +++ /dev/null @@ -1,9 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export * from './constants'; diff --git a/src/plugins/maps_oss/config.ts b/src/plugins/maps_oss/config.ts deleted file mode 100644 index 58c50f0104f46..0000000000000 --- a/src/plugins/maps_oss/config.ts +++ /dev/null @@ -1,15 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { schema, TypeOf } from '@kbn/config-schema'; - -export const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), -}); - -export type ConfigSchema = TypeOf; diff --git a/src/plugins/maps_oss/kibana.json b/src/plugins/maps_oss/kibana.json deleted file mode 100644 index 19770dcffaadd..0000000000000 --- a/src/plugins/maps_oss/kibana.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "mapsOss", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": [ - "visualizations" - ], - "extraPublicDirs": ["common/constants"] -} diff --git a/src/plugins/maps_oss/public/index.ts b/src/plugins/maps_oss/public/index.ts deleted file mode 100644 index 1d27dc4b6d996..0000000000000 --- a/src/plugins/maps_oss/public/index.ts +++ /dev/null @@ -1,11 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { MapsOSSPlugin } from './plugin'; - -export const plugin = () => new MapsOSSPlugin(); diff --git a/src/plugins/maps_oss/public/plugin.ts b/src/plugins/maps_oss/public/plugin.ts deleted file mode 100644 index 5e27ae34257bf..0000000000000 --- a/src/plugins/maps_oss/public/plugin.ts +++ /dev/null @@ -1,32 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { DocLinksStart, CoreSetup } from 'src/core/public'; -import { VisualizationsSetup } from '../../visualizations/public'; -import { getMapsAliasConfig } from './vis_type_alias'; - -export interface MapsPluginSetupDependencies { - visualizations: VisualizationsSetup; -} - -export interface MapsPluginStartDependencies { - docLinks: DocLinksStart; -} - -export class MapsOSSPlugin { - setup( - core: CoreSetup, - { visualizations }: MapsPluginSetupDependencies - ) { - core.getStartServices().then(([coreStart]) => { - visualizations.registerAlias(getMapsAliasConfig(coreStart.docLinks)); - }); - } - - start() {} -} diff --git a/src/plugins/maps_oss/public/vis_type_alias.ts b/src/plugins/maps_oss/public/vis_type_alias.ts deleted file mode 100644 index a27c628755cf6..0000000000000 --- a/src/plugins/maps_oss/public/vis_type_alias.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { VisTypeAlias } from 'src/plugins/visualizations/public'; -import { DocLinksStart } from 'src/core/public'; -import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common'; - -export const getMapsAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({ - aliasPath: APP_PATH, - aliasApp: APP_NAME, - name: PLUGIN_ID_OSS, - title: i18n.translate('mapsOss.visTypeAlias.title', { - defaultMessage: 'Maps', - }), - description: i18n.translate('mapsOss.visTypeAlias.description', { - defaultMessage: 'Plot and style your geo data in a multi layer map.', - }), - icon: APP_ICON, - stage: 'production', - disabled: true, - promoTooltip: { - description: i18n.translate('mapsOss.visTypeAlias.promoTooltip.description', { - defaultMessage: 'Try maps for free with Elastic. Learn more.', - }), - link: `${links.visualize.maps}?blade=kibanaossvizwizard`, - }, -}); diff --git a/src/plugins/maps_oss/server/index.ts b/src/plugins/maps_oss/server/index.ts deleted file mode 100644 index 8f07beee705a6..0000000000000 --- a/src/plugins/maps_oss/server/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { PluginConfigDescriptor } from 'kibana/server'; -import { copyFromRoot } from '@kbn/config'; -import { configSchema, ConfigSchema } from '../config'; - -export const config: PluginConfigDescriptor = { - schema: configSchema, - deprecations: () => [copyFromRoot('xpack.maps.enabled', 'maps_oss.enabled')], -}; - -export const plugin = () => ({ - setup() {}, - start() {}, -}); diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index 839d2b1f57c34..cb6bc624801d9 100644 --- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -31,11 +31,6 @@ export interface VisualizationsAppExtension { toListItem: (savedObject: SavedObject) => VisualizationListItem; } -export interface VisTypeAliasPromoTooltip { - description: string; - link: string; -} - export interface VisTypeAlias { aliasPath: string; aliasApp: string; @@ -43,10 +38,8 @@ export interface VisTypeAlias { title: string; icon: string; promotion?: boolean; - promoTooltip?: VisTypeAliasPromoTooltip; description: string; note?: string; - disabled?: boolean; getSupportedTriggers?: () => string[]; stage: VisualizationStage; diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx index 74163296e31fd..396be30aca6d0 100644 --- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx @@ -39,11 +39,6 @@ describe('GroupSelection', () => { title: 'Vis with alias Url', aliasApp: 'aliasApp', aliasPath: '#/aliasApp', - disabled: true, - promoTooltip: { - description: 'Learn More', - link: '#/anotherUrl', - }, description: 'Vis with alias Url', stage: 'production', group: VisGroups.PROMOTED, @@ -227,41 +222,6 @@ describe('GroupSelection', () => { ]); }); - it('should render disabled aliases with a disabled class', () => { - const wrapper = mountWithIntl( - - ); - expect(wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').exists()).toBe(true); - expect( - wrapper - .find('[data-test-subj="visType-visWithAliasUrl"]') - .at(1) - .hasClass('euiCard-isDisabled') - ).toBe(true); - }); - - it('should render a basic badge with link for disabled aliases with promoTooltip', () => { - const wrapper = mountWithIntl( - - ); - expect(wrapper.find('[data-test-subj="visTypeBadge"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="visTypeBadge"]').at(0).prop('tooltipContent')).toBe( - 'Learn More' - ); - }); - it('should not show tools experimental visualizations if showExperimentalis false', () => { const expVis = { name: 'visExp', diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx index 9b61b2c415e9f..594e37f6a7608 100644 --- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx @@ -48,10 +48,6 @@ interface VisCardProps { showExperimental?: boolean | undefined; } -function isVisTypeAlias(type: BaseVisType | VisTypeAlias): type is VisTypeAlias { - return 'aliasPath' in type; -} - function GroupSelection(props: GroupSelectionProps) { const visualizeGuideLink = props.docLinks.links.dashboard.guide; const promotedVisGroups = useMemo( @@ -185,29 +181,8 @@ const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => { const onClick = useCallback(() => { onVisTypeSelected(visType); }, [onVisTypeSelected, visType]); - const shouldDisableCard = isVisTypeAlias(visType) && visType.disabled; - const betaBadgeContent = - shouldDisableCard && 'promoTooltip' in visType ? ( - - - - ) : undefined; return ( - {betaBadgeContent} { } onClick={onClick} - isDisabled={shouldDisableCard} data-test-subj={`visType-${visType.name}`} data-vis-stage={!('aliasPath' in visType) ? visType.stage : 'alias'} aria-label={`visType-${visType.name}`} diff --git a/test/functional/apps/visualize/_chart_types.ts b/test/functional/apps/visualize/_chart_types.ts index 55b68b7370148..69403f2090594 100644 --- a/test/functional/apps/visualize/_chart_types.ts +++ b/test/functional/apps/visualize/_chart_types.ts @@ -12,21 +12,17 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const deployment = getService('deployment'); const log = getService('log'); const PageObjects = getPageObjects(['visualize']); - let isOss = true; describe('chart types', function () { before(async function () { log.debug('navigateToApp visualize'); - isOss = await deployment.isOss(); await PageObjects.visualize.navigateToNewVisualization(); }); it('should show the promoted vis types for the first step', async function () { const expectedChartTypes = ['Custom visualization', 'Lens', 'Maps', 'TSVB']; - log.debug('oss= ' + isOss); // find all the chart types and make sure there all there const chartTypes = (await PageObjects.visualize.getPromotedVisTypes()).sort(); @@ -37,9 +33,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show the correct agg based chart types', async function () { await PageObjects.visualize.clickAggBasedVisualizations(); - let expectedChartTypes = [ + const expectedChartTypes = [ 'Area', - 'Coordinate Map', 'Data table', 'Gauge', 'Goal', @@ -48,21 +43,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Line', 'Metric', 'Pie', - 'Region Map', 'Tag cloud', 'Timelion', 'Vertical bar', ]; - if (!isOss) { - expectedChartTypes = _.remove(expectedChartTypes, function (n) { - return n !== 'Coordinate Map'; - }); - expectedChartTypes = _.remove(expectedChartTypes, function (n) { - return n !== 'Region Map'; - }); - expectedChartTypes.sort(); - } - log.debug('oss= ' + isOss); // find all the chart types and make sure there all there const chartTypes = (await PageObjects.visualize.getChartTypes()).sort(); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 8dd2854419693..4170ada692e67 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -67,11 +67,15 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { this.tags('ciGroup9'); loadTestFile(require.resolve('./_embedding_chart')); - loadTestFile(require.resolve('./_chart_types')); loadTestFile(require.resolve('./_area_chart')); loadTestFile(require.resolve('./_data_table')); loadTestFile(require.resolve('./_data_table_nontimeindex')); loadTestFile(require.resolve('./_data_table_notimeindex_filters')); + + // this check is not needed when the CI doesn't run anymore for the OSS + if (!isOss) { + loadTestFile(require.resolve('./_chart_types')); + } }); describe('', function () { diff --git a/tsconfig.json b/tsconfig.json index b6742bffeab55..e7856aa0c8747 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,6 @@ "src/plugins/kibana_react/**/*", "src/plugins/kibana_usage_collection/**/*", "src/plugins/kibana_utils/**/*", - "src/plugins/lens_oss/**/*", "src/plugins/management/**/*", "src/plugins/navigation/**/*", "src/plugins/newsfeed/**/*", @@ -81,7 +80,6 @@ { "path": "./src/plugins/kibana_react/tsconfig.json" }, { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/kibana_utils/tsconfig.json" }, - { "path": "./src/plugins/lens_oss/tsconfig.json" }, { "path": "./src/plugins/management/tsconfig.json" }, { "path": "./src/plugins/navigation/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 1bce19e2ee44a..5edfd4231a6d5 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -22,7 +22,6 @@ { "path": "./src/plugins/kibana_react/tsconfig.json" }, { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/kibana_utils/tsconfig.json" }, - { "path": "./src/plugins/lens_oss/tsconfig.json" }, { "path": "./src/plugins/management/tsconfig.json" }, { "path": "./src/plugins/navigation/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index dc0a92ac702d0..9df3f41fbd855 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -19,5 +19,5 @@ "optionalPlugins": ["usageCollection", "taskManager", "globalSearch", "savedObjectsTagging"], "configPath": ["xpack", "lens"], "extraPublicDirs": ["common/constants"], - "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "lensOss", "presentationUtil"] + "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "presentationUtil"] } diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index cdffdb342fd23..3fb7186aeac59 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -39,7 +39,6 @@ import { VISUALIZE_FIELD_TRIGGER, } from '../../../../src/plugins/ui_actions/public'; import { getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; -import { PLUGIN_ID_OSS } from '../../../../src/plugins/lens_oss/common/constants'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; @@ -208,8 +207,6 @@ export class LensPlugin { start(core: CoreStart, startDependencies: LensPluginStartDependencies): LensPublicStart { const frameStart = this.editorFrameService.start(core, startDependencies); this.createEditorFrame = frameStart.createInstance; - // unregisters the OSS alias - startDependencies.visualizations.unRegisterAlias(PLUGIN_ID_OSS); // unregisters the Visualize action and registers the lens one if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) { startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 7ac5a2980d0ba..636d2f44b0217 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -35,7 +35,6 @@ { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/embeddable/tsconfig.json"}, - { "path": "../../../src/plugins/lens_oss/tsconfig.json"}, { "path": "../../../src/plugins/presentation_util/tsconfig.json"}, ] } \ No newline at end of file diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index 5f6f5224e32a3..2536601d0e6b1 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -23,5 +23,5 @@ "ui": true, "server": true, "extraPublicDirs": ["common/constants"], - "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "mapsOss", "presentationUtil"] + "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "presentationUtil"] } diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index dd256126fae62..4173328a41d57 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -34,7 +34,6 @@ import { VisualizationsStart, } from '../../../../src/plugins/visualizations/public'; import { APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; -import { PLUGIN_ID_OSS } from '../../../../src/plugins/maps_oss/common/constants'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public'; import { createMapsUrlGenerator, @@ -162,9 +161,6 @@ export class MapsPlugin setLicensingPluginStart(plugins.licensing); setStartServices(core, plugins); - // unregisters the OSS alias - plugins.visualizations.unRegisterAlias(PLUGIN_ID_OSS); - if (core.application.capabilities.maps.show) { plugins.uiActions.addTriggerAction(VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldAction); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e28b41e0ce5e3..da9228335a3f3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3032,10 +3032,6 @@ "kibanaOverview.manageData.sectionTitle": "データを管理", "kibanaOverview.more.title": "Elasticではさまざまなことが可能です", "kibanaOverview.news.title": "新機能", - "lensOss.visTypeAlias.description": "ドラッグアンドドロップエディターでビジュアライゼーションを作成します。いつでもビジュアライゼーションタイプを切り替えることができます。ほとんどのビジュアライゼーションに最適です。", - "lensOss.visTypeAlias.note": "ほとんどのユーザーに推奨されます。", - "lensOss.visTypeAlias.promoTooltip.description": "Elastic では Lens を無料でお試しいただけます。詳細情報", - "lensOss.visTypeAlias.title": "レンズ", "management.breadcrumb": "スタック管理", "management.landing.header": "Stack Management {version}へようこそ", "management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibanaの設定、その他を管理します。", @@ -3089,9 +3085,6 @@ "maps_legacy.wmsOptions.wmsStylesLabel": "WMSスタイル", "maps_legacy.wmsOptions.wmsUrlLabel": "WMS URL", "maps_legacy.wmsOptions.wmsVersionLabel": "WMS バージョン", - "mapsOss.visTypeAlias.description": "マルチレイヤーマップで地理データをプロットしてスタイル設定できます。", - "mapsOss.visTypeAlias.promoTooltip.description": "Elastic では Maps を無料でお試しいただけます。詳細情報", - "mapsOss.visTypeAlias.title": "マップ", "monaco.painlessLanguage.autocomplete.docKeywordDescription": "doc['field_name'] 構文を使用して、スクリプトからフィールド値にアクセスします", "monaco.painlessLanguage.autocomplete.emitKeywordDescription": "戻らずに値を発行します。", "monaco.painlessLanguage.autocomplete.fieldValueDescription": "フィールド「{fieldName}」の値を取得します", @@ -4692,7 +4685,6 @@ "visualizations.initializeWithoutIndexPatternErrorMessage": "インデックスパターンなしで集約を初期化しようとしています", "visualizations.newVisWizard.aggBasedGroupDescription": "クラシック Visualize ライブラリを使用して、アグリゲーションに基づいてグラフを作成します。", "visualizations.newVisWizard.aggBasedGroupTitle": "アグリゲーションに基づく", - "visualizations.newVisWizard.basicTitle": "基本", "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", "visualizations.newVisWizard.experimentalTitle": "実験的", "visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 49fd9ce095f66..679edaf9e0cdd 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3036,10 +3036,6 @@ "kibanaOverview.manageData.sectionTitle": "管理您的数据", "kibanaOverview.more.title": "Elastic 让您事半功倍", "kibanaOverview.news.title": "最新动态", - "lensOss.visTypeAlias.description": "使用我们支持拖放的编辑器来创建可视化。随时在可视化类型之间切换。绝大多数可视化的最佳选择。", - "lensOss.visTypeAlias.note": "适合绝大多数用户。", - "lensOss.visTypeAlias.promoTooltip.description": "免费试用 Elastic 的 Lens。了解详情。", - "lensOss.visTypeAlias.title": "Lens", "management.breadcrumb": "Stack Management", "management.landing.header": "欢迎使用 Stack Management {version}", "management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", @@ -3093,9 +3089,6 @@ "maps_legacy.wmsOptions.wmsStylesLabel": "WMS 样式", "maps_legacy.wmsOptions.wmsUrlLabel": "WMS url", "maps_legacy.wmsOptions.wmsVersionLabel": "WMS 版本", - "mapsOss.visTypeAlias.description": "在多层地图中绘制地理数据并设置其样式。", - "mapsOss.visTypeAlias.promoTooltip.description": "免费试用 Elastic 的地图。了解详情。", - "mapsOss.visTypeAlias.title": "地图", "monaco.painlessLanguage.autocomplete.docKeywordDescription": "使用 doc['field_name'] 语法,从脚本中访问字段值", "monaco.painlessLanguage.autocomplete.emitKeywordDescription": "发出值,而不返回值。", "monaco.painlessLanguage.autocomplete.fieldValueDescription": "检索字段“{fieldName}”的值", @@ -4697,7 +4690,6 @@ "visualizations.initializeWithoutIndexPatternErrorMessage": "正在尝试在不使用索引模式的情况下初始化聚合", "visualizations.newVisWizard.aggBasedGroupDescription": "使用我们的经典可视化库,基于聚合创建图表。", "visualizations.newVisWizard.aggBasedGroupTitle": "基于聚合", - "visualizations.newVisWizard.basicTitle": "基本级", "visualizations.newVisWizard.chooseSourceTitle": "选择源", "visualizations.newVisWizard.experimentalTitle": "实验性", "visualizations.newVisWizard.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。", From b8947e3e1574d69cc91b297743908b48daf6acba Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 27 Jan 2021 11:52:13 +0100 Subject: [PATCH 16/16] [Search Sessions] Make search session indicator UI opt-in, refactor per-app capabilities (#88699) --- .../kibana-plugin-plugins-data-public.md | 1 + ...nosearchsessionstoragecapabilitymessage.md | 13 ++++ .../public/application/dashboard_router.tsx | 1 + .../embeddable/dashboard_container.tsx | 3 +- .../hooks/use_dashboard_state_manager.ts | 15 ++++- .../dashboard/public/application/types.ts | 1 + src/plugins/data/public/index.ts | 1 + src/plugins/data/public/public.api.md | 35 ++++++----- src/plugins/data/public/search/index.ts | 1 + .../data/public/search/session/i18n.ts | 20 +++++++ .../data/public/search/session/index.ts | 1 + .../data/public/search/session/mocks.ts | 4 +- .../search/session/session_service.test.ts | 60 ++++++++++++++++++- .../public/search/session/session_service.ts | 59 ++++++++++++++---- .../public/application/angular/discover.js | 14 ++++- ...onnected_search_session_indicator.test.tsx | 37 +++++++++--- .../connected_search_session_indicator.tsx | 29 +++------ .../config.ts | 1 + .../services/index.ts | 4 +- ...nd_to_background.ts => search_sessions.ts} | 26 +++++--- .../async_search/sessions_in_space.ts | 57 +++++++++++++++++- .../tests/apps/discover/sessions_in_space.ts | 59 +++++++++++++++++- .../tests/apps/lens/index.ts | 22 +++++++ .../tests/apps/lens/search_sessions.ts | 35 +++++++++++ 24 files changed, 424 insertions(+), 75 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md create mode 100644 src/plugins/data/public/search/session/i18n.ts rename x-pack/test/send_search_to_background_integration/services/{send_to_background.ts => search_sessions.ts} (81%) create mode 100644 x-pack/test/send_search_to_background_integration/tests/apps/lens/index.ts create mode 100644 x-pack/test/send_search_to_background_integration/tests/apps/lens/search_sessions.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 65a722868b37f..4bbc76b78ba03 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -125,6 +125,7 @@ | [isPartialResponse](./kibana-plugin-plugins-data-public.ispartialresponse.md) | | | [isQuery](./kibana-plugin-plugins-data-public.isquery.md) | | | [isTimeRange](./kibana-plugin-plugins-data-public.istimerange.md) | | +| [noSearchSessionStorageCapabilityMessage](./kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md) | Message to display in case storing session session is disabled due to turned off capability | | [parseSearchSourceJSON](./kibana-plugin-plugins-data-public.parsesearchsourcejson.md) | | | [QueryStringInput](./kibana-plugin-plugins-data-public.querystringinput.md) | | | [search](./kibana-plugin-plugins-data-public.search.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md new file mode 100644 index 0000000000000..2bb0a0db8f9b3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [noSearchSessionStorageCapabilityMessage](./kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md) + +## noSearchSessionStorageCapabilityMessage variable + +Message to display in case storing session session is disabled due to turned off capability + +Signature: + +```typescript +noSearchSessionStorageCapabilityMessage: string +``` diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index 9141f2e592fd7..5206c76f50be2 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -104,6 +104,7 @@ export async function mountApp({ mapsCapabilities: { save: Boolean(coreStart.application.capabilities.maps?.save) }, createShortUrl: Boolean(coreStart.application.capabilities.dashboard.createShortUrl), visualizeCapabilities: { save: Boolean(coreStart.application.capabilities.visualize?.save) }, + storeSearchSession: Boolean(coreStart.application.capabilities.dashboard.storeSearchSession), }, }; diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index fa520ec22497b..780eb1bad8c2b 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -89,7 +89,7 @@ export interface InheritedChildInput extends IndexSignature { export type DashboardReactContextValue = KibanaReactContextValue; export type DashboardReactContext = KibanaReactContext; -const defaultCapabilities = { +const defaultCapabilities: DashboardCapabilities = { show: false, createNew: false, saveQuery: false, @@ -97,6 +97,7 @@ const defaultCapabilities = { hideWriteControls: true, mapsCapabilities: { save: false }, visualizeCapabilities: { save: false }, + storeSearchSession: true, }; export class DashboardContainer extends Container { diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts index a4044e8668e59..93fbb50950850 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts @@ -16,6 +16,7 @@ import { useKibana } from '../../services/kibana_react'; import { connectToQueryState, esFilters, + noSearchSessionStorageCapabilityMessage, QueryState, syncQueryStateWithUrl, } from '../../services/data'; @@ -159,13 +160,22 @@ export const useDashboardStateManager = ( stateManager.isNew() ); - searchSession.setSearchSessionInfoProvider( + searchSession.enableStorage( createSessionRestorationDataProvider({ data: dataPlugin, getDashboardTitle: () => dashboardTitle, getDashboardId: () => savedDashboard?.id || '', getAppState: () => stateManager.getAppState(), - }) + }), + { + isDisabled: () => + dashboardCapabilities.storeSearchSession + ? { disabled: false } + : { + disabled: true, + reasonText: noSearchSessionStorageCapabilityMessage, + }, + } ); setDashboardStateManager(stateManager); @@ -192,6 +202,7 @@ export const useDashboardStateManager = ( toasts, uiSettings, usageCollection, + dashboardCapabilities.storeSearchSession, ]); return { dashboardStateManager, viewMode, setViewMode }; diff --git a/src/plugins/dashboard/public/application/types.ts b/src/plugins/dashboard/public/application/types.ts index 61e16beed61f4..e4f9388a919d1 100644 --- a/src/plugins/dashboard/public/application/types.ts +++ b/src/plugins/dashboard/public/application/types.ts @@ -55,6 +55,7 @@ export interface DashboardCapabilities { saveQuery: boolean; createNew: boolean; show: boolean; + storeSearchSession: boolean; } export interface DashboardAppServices { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index ff3e2ebc89a41..fc8c44e8d1870 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -384,6 +384,7 @@ export { SearchTimeoutError, TimeoutErrorMode, PainlessError, + noSearchSessionStorageCapabilityMessage, } from './search'; export type { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 002f033365790..9e493f46b0781 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1834,6 +1834,11 @@ export enum METRIC_TYPES { TOP_HITS = "top_hits" } +// Warning: (ae-missing-release-tag) "noSearchSessionStorageCapabilityMessage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const noSearchSessionStorageCapabilityMessage: string; + // Warning: (ae-missing-release-tag) "OptionedParamType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2629,21 +2634,21 @@ export const UI_SETTINGS: { // 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 "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:399:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:427: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:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:41:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 1deffc9c2d55e..3d87411883a67 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -37,6 +37,7 @@ export { SearchSessionState, SessionsClient, ISessionsClient, + noSearchSessionStorageCapabilityMessage, } from './session'; export { getEsPreference } from './es_search'; diff --git a/src/plugins/data/public/search/session/i18n.ts b/src/plugins/data/public/search/session/i18n.ts new file mode 100644 index 0000000000000..2ee36b46dfd5a --- /dev/null +++ b/src/plugins/data/public/search/session/i18n.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +/** + * Message to display in case storing + * session session is disabled due to turned off capability + */ +export const noSearchSessionStorageCapabilityMessage = i18n.translate( + 'data.searchSessionIndicator.noCapability', + { + defaultMessage: "You don't have permissions to create search sessions.", + } +); diff --git a/src/plugins/data/public/search/session/index.ts b/src/plugins/data/public/search/session/index.ts index ab311d56fe096..f3f0c34c1be75 100644 --- a/src/plugins/data/public/search/session/index.ts +++ b/src/plugins/data/public/search/session/index.ts @@ -9,3 +9,4 @@ export { SessionService, ISessionService, SearchSessionInfoProvider } from './session_service'; export { SearchSessionState } from './search_session_state'; export { SessionsClient, ISessionsClient } from './sessions_client'; +export { noSearchSessionStorageCapabilityMessage } from './i18n'; diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index 6a7a207b90d46..679898e3e51dd 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -29,7 +29,6 @@ export function getSessionServiceMock(): jest.Mocked { getSessionId: jest.fn(), getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), state$: new BehaviorSubject(SearchSessionState.None).asObservable(), - setSearchSessionInfoProvider: jest.fn(), trackSearch: jest.fn((searchDescriptor) => () => {}), destroy: jest.fn(), onRefresh$: new Subject(), @@ -40,5 +39,8 @@ export function getSessionServiceMock(): jest.Mocked { save: jest.fn(), isCurrentSession: jest.fn(), getSearchOptions: jest.fn(), + enableStorage: jest.fn(), + isSessionStorageReady: jest.fn(() => true), + getSearchSessionIndicatorUiConfig: jest.fn(() => ({ isDisabled: () => ({ disabled: false }) })), }; } diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index 7797d92c4e07d..21c38c68e6a83 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -113,7 +113,7 @@ describe('Session service', () => { sessionId, }); - sessionService.setSearchSessionInfoProvider({ + sessionService.enableStorage({ getName: async () => 'Name', getUrlGeneratorData: async () => ({ urlGeneratorId: 'id', @@ -156,4 +156,62 @@ describe('Session service', () => { expect(sessionService.isCurrentSession('some-other')).toBeFalsy(); expect(sessionService.isCurrentSession(sessionId)).toBeTruthy(); }); + + test('enableStorage() enables storage capabilities', async () => { + sessionService.start(); + await expect(() => sessionService.save()).rejects.toThrowErrorMatchingInlineSnapshot( + `"No info provider for current session"` + ); + + expect(sessionService.isSessionStorageReady()).toBe(false); + + sessionService.enableStorage({ + getName: async () => 'Name', + getUrlGeneratorData: async () => ({ + urlGeneratorId: 'id', + initialState: {}, + restoreState: {}, + }), + }); + + expect(sessionService.isSessionStorageReady()).toBe(true); + + await expect(() => sessionService.save()).resolves; + + sessionService.clear(); + expect(sessionService.isSessionStorageReady()).toBe(false); + }); + + test('can provide config for search session indicator', () => { + expect(sessionService.getSearchSessionIndicatorUiConfig().isDisabled().disabled).toBe(false); + sessionService.enableStorage( + { + getName: async () => 'Name', + getUrlGeneratorData: async () => ({ + urlGeneratorId: 'id', + initialState: {}, + restoreState: {}, + }), + }, + { + isDisabled: () => ({ disabled: true, reasonText: 'text' }), + } + ); + + expect(sessionService.getSearchSessionIndicatorUiConfig().isDisabled().disabled).toBe(true); + + sessionService.clear(); + expect(sessionService.getSearchSessionIndicatorUiConfig().isDisabled().disabled).toBe(false); + }); + + test('save() throws in case getUrlGeneratorData returns throws', async () => { + sessionService.enableStorage({ + getName: async () => 'Name', + getUrlGeneratorData: async () => { + throw new Error('Haha'); + }, + }); + sessionService.start(); + await expect(() => sessionService.save()).rejects.toMatchInlineSnapshot(`[Error: Haha]`); + }); }); diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index 6269398036e76..23129a9afc460 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -43,6 +43,20 @@ export interface SearchSessionInfoProvider; } +/** + * Configure a "Search session indicator" UI + */ +export interface SearchSessionIndicatorUiConfig { + /** + * App controls if "Search session indicator" UI should be disabled. + * reasonText will appear in a tooltip. + * + * Could be used, for example, to disable "Search session indicator" UI + * in case user doesn't have permissions to store a search session + */ + isDisabled: () => { disabled: true; reasonText: string } | { disabled: false }; +} + /** * Responsible for tracking a current search session. Supports only a single session at a time. */ @@ -51,6 +65,7 @@ export class SessionService { private readonly state: SessionStateContainer; private searchSessionInfoProvider?: SearchSessionInfoProvider; + private searchSessionIndicatorUiConfig?: Partial; private subscription = new Subscription(); private curApp?: string; @@ -102,17 +117,6 @@ export class SessionService { }); } - /** - * Set a provider of info about current session - * This will be used for creating a search session saved object - * @param searchSessionInfoProvider - */ - public setSearchSessionInfoProvider( - searchSessionInfoProvider: SearchSessionInfoProvider | undefined - ) { - this.searchSessionInfoProvider = searchSessionInfoProvider; - } - /** * Used to track pending searches within current session * @@ -185,7 +189,8 @@ export class SessionService { */ public clear() { this.state.transitions.clear(); - this.setSearchSessionInfoProvider(undefined); + this.searchSessionInfoProvider = undefined; + this.searchSessionIndicatorUiConfig = undefined; } private refresh$ = new Subject(); @@ -269,4 +274,34 @@ export class SessionService { isStored: isCurrentSession ? this.isStored() : false, }; } + + /** + * Provide an info about current session which is needed for storing a search session. + * To opt-into "Search session indicator" UI app has to call {@link enableStorage}. + * + * @param searchSessionInfoProvider - info provider for saving a search session + * @param searchSessionIndicatorUiConfig - config for "Search session indicator" UI + */ + public enableStorage( + searchSessionInfoProvider: SearchSessionInfoProvider, + searchSessionIndicatorUiConfig?: SearchSessionIndicatorUiConfig + ) { + this.searchSessionInfoProvider = searchSessionInfoProvider; + this.searchSessionIndicatorUiConfig = searchSessionIndicatorUiConfig; + } + + /** + * If the current app explicitly called {@link enableStorage} and provided all configuration needed + * for storing its search sessions + */ + public isSessionStorageReady(): boolean { + return !!this.searchSessionInfoProvider; + } + + public getSearchSessionIndicatorUiConfig(): SearchSessionIndicatorUiConfig { + return { + isDisabled: () => ({ disabled: false }), + ...this.searchSessionIndicatorUiConfig, + }; + } } diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 946baa7f4ecb1..5c26680c7cc45 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -18,6 +18,7 @@ import { connectToQueryState, esFilters, indexPatterns as indexPatternsUtils, + noSearchSessionStorageCapabilityMessage, syncQueryStateWithUrl, } from '../../../../data/public'; import { getSortArray } from './doc_table'; @@ -284,12 +285,21 @@ function discoverController($route, $scope, Promise) { } }); - data.search.session.setSearchSessionInfoProvider( + data.search.session.enableStorage( createSearchSessionRestorationDataProvider({ appStateContainer, data, getSavedSearch: () => savedSearch, - }) + }), + { + isDisabled: () => + capabilities.discover.storeSearchSession + ? { disabled: false } + : { + disabled: true, + reasonText: noSearchSessionStorageCapabilityMessage, + }, + } ); $scope.setIndexPattern = async (id) => { diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx index 2c74f9c995a5a..f4bb7577bee53 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -28,6 +28,12 @@ timeFilter.getRefreshInterval.mockImplementation(() => refreshInterval$.getValue beforeEach(() => { refreshInterval$.next({ value: 0, pause: true }); + sessionService.isSessionStorageReady.mockImplementation(() => true); + sessionService.getSearchSessionIndicatorUiConfig.mockImplementation(() => ({ + isDisabled: () => ({ + disabled: false, + }), + })); }); test("shouldn't show indicator in case no active search session", async () => { @@ -45,6 +51,22 @@ test("shouldn't show indicator in case no active search session", async () => { expect(container).toMatchInlineSnapshot(`
`); }); +test("shouldn't show indicator in case app hasn't opt-in", async () => { + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ + sessionService, + application: coreStart.application, + timeFilter, + }); + const { getByTestId, container } = render(); + sessionService.isSessionStorageReady.mockImplementation(() => false); + + // make sure `searchSessionIndicator` isn't appearing after some time (lazy-loading) + await expect( + waitFor(() => getByTestId('searchSessionIndicator'), { timeout: 100 }) + ).rejects.toThrow(); + expect(container).toMatchInlineSnapshot(`
`); +}); + test('should show indicator in case there is an active search session', async () => { const state$ = new BehaviorSubject(SearchSessionState.Loading); const SearchSessionIndicator = createConnectedSearchSessionIndicator({ @@ -57,7 +79,7 @@ test('should show indicator in case there is an active search session', async () await waitFor(() => getByTestId('searchSessionIndicator')); }); -test('should be disabled when permissions are off', async () => { +test('should be disabled in case uiConfig says so ', async () => { const state$ = new BehaviorSubject(SearchSessionState.Loading); coreStart.application.currentAppId$ = new BehaviorSubject('discover'); (coreStart.application.capabilities as any) = { @@ -65,6 +87,12 @@ test('should be disabled when permissions are off', async () => { storeSearchSession: false, }, }; + sessionService.getSearchSessionIndicatorUiConfig.mockImplementation(() => ({ + isDisabled: () => ({ + disabled: true, + reasonText: 'reason', + }), + })); const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, @@ -80,12 +108,7 @@ test('should be disabled when permissions are off', async () => { test('should be disabled during auto-refresh', async () => { const state$ = new BehaviorSubject(SearchSessionState.Loading); - coreStart.application.currentAppId$ = new BehaviorSubject('discover'); - (coreStart.application.capabilities as any) = { - discover: { - storeSearchSession: true, - }, - }; + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx index 5c8c01064bff4..59c1bb4a223b1 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx @@ -29,35 +29,14 @@ export const createConnectedSearchSessionIndicator = ({ .getRefreshIntervalUpdate$() .pipe(map(isAutoRefreshEnabled), distinctUntilChanged()); - const getCapabilitiesByAppId = ( - capabilities: ApplicationStart['capabilities'], - appId?: string - ) => { - switch (appId) { - case 'dashboards': - return capabilities.dashboard; - case 'discover': - return capabilities.discover; - default: - return undefined; - } - }; - return () => { const state = useObservable(sessionService.state$.pipe(debounceTime(500))); const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled()); - const appId = useObservable(application.currentAppId$, undefined); + const isDisabledByApp = sessionService.getSearchSessionIndicatorUiConfig().isDisabled(); let disabled = false; let disabledReasonText: string = ''; - if (getCapabilitiesByAppId(application.capabilities, appId)?.storeSearchSession !== true) { - disabled = true; - disabledReasonText = i18n.translate('xpack.data.searchSessionIndicator.noCapability', { - defaultMessage: "You don't have permissions to send to background.", - }); - } - if (autoRefreshEnabled) { disabled = true; disabledReasonText = i18n.translate( @@ -68,6 +47,12 @@ export const createConnectedSearchSessionIndicator = ({ ); } + if (isDisabledByApp.disabled) { + disabled = true; + disabledReasonText = isDisabledByApp.reasonText; + } + + if (!sessionService.isSessionStorageReady()) return null; if (!state) return null; return ( diff --git a/x-pack/test/send_search_to_background_integration/config.ts b/x-pack/test/send_search_to_background_integration/config.ts index bad818bb69664..c788cc38477e6 100644 --- a/x-pack/test/send_search_to_background_integration/config.ts +++ b/x-pack/test/send_search_to_background_integration/config.ts @@ -24,6 +24,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { resolve(__dirname, './tests/apps/dashboard/async_search'), resolve(__dirname, './tests/apps/discover'), resolve(__dirname, './tests/apps/management/search_sessions'), + resolve(__dirname, './tests/apps/lens'), ], kbnTestServer: { diff --git a/x-pack/test/send_search_to_background_integration/services/index.ts b/x-pack/test/send_search_to_background_integration/services/index.ts index 35eed5a218b42..b177ac3a01cb0 100644 --- a/x-pack/test/send_search_to_background_integration/services/index.ts +++ b/x-pack/test/send_search_to_background_integration/services/index.ts @@ -5,9 +5,9 @@ */ import { services as functionalServices } from '../../functional/services'; -import { SendToBackgroundProvider } from './send_to_background'; +import { SearchSessionsProvider } from './search_sessions'; export const services = { ...functionalServices, - searchSessions: SendToBackgroundProvider, + searchSessions: SearchSessionsProvider, }; diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/search_sessions.ts similarity index 81% rename from x-pack/test/send_search_to_background_integration/services/send_to_background.ts rename to x-pack/test/send_search_to_background_integration/services/search_sessions.ts index 8c3261c2074ae..7041de6462243 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/search_sessions.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import expect from '@kbn/expect'; import { SavedObjectsFindResponse } from 'src/core/server'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -20,7 +21,7 @@ type SessionStateType = | 'restored' | 'canceled'; -export function SendToBackgroundProvider({ getService }: FtrProviderContext) { +export function SearchSessionsProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const log = getService('log'); const retry = getService('retry'); @@ -36,6 +37,17 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { return testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ); } + public async missingOrFail(): Promise { + return testSubjects.missingOrFail(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + } + + public async disabledOrFail() { + await this.exists(); + await expect(await (await (await this.find()).findByTagName('button')).isEnabled()).to.be( + false + ); + } + public async expectState(state: SessionStateType) { return retry.waitFor(`searchSessions indicator to get into state = ${state}`, async () => { const currentState = await ( @@ -93,12 +105,12 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { } /* - * This cleanup function should be used by tests that create new background sesions. - * Tests should not end with new background sessions remaining in storage since that interferes with functional tests that check the _find API. - * Alternatively, a test can navigate to `Managment > Search Sessions` and use the UI to delete any created tests. + * This cleanup function should be used by tests that create new search sessions. + * Tests should not end with new search sessions remaining in storage since that interferes with functional tests that check the _find API. + * Alternatively, a test can navigate to `Management > Search Sessions` and use the UI to delete any created tests. */ public async deleteAllSearchSessions() { - log.debug('Deleting created background sessions'); + log.debug('Deleting created searcg sessions'); // ignores 409 errs and keeps retrying await retry.tryForTime(10000, async () => { const { body } = await supertest @@ -109,10 +121,10 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { .expect(200); const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; - log.debug(`Found created background sessions: ${savedObjects.map(({ id }) => id)}`); + log.debug(`Found created search sessions: ${savedObjects.map(({ id }) => id)}`); await Promise.all( savedObjects.map(async (so) => { - log.debug(`Deleting background session: ${so.id}`); + log.debug(`Deleting search session: ${so.id}`); await supertest .delete(`/internal/session/${so.id}`) .set(`kbn-xsrf`, `anything`) diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts index f590e44138642..6aea4368a2f3f 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const searchSessions = getService('searchSessions'); describe('dashboard in space', () => { - describe('Send to background in space', () => { + describe('Storing search sessions in space', () => { before(async () => { await esArchiver.load('dashboard/session_in_space'); @@ -92,5 +92,60 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.missingOrFail('embeddableErrorLabel'); }); }); + + describe('Disabled storing search sessions', () => { + before(async () => { + await esArchiver.load('dashboard/session_in_space'); + + await security.role.create('data_analyst', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['all'] }], + }, + kibana: [ + { + feature: { + dashboard: ['minimal_read'], + }, + spaces: ['another-space'], + }, + ], + }); + + await security.user.create('analyst', { + password: 'analyst-password', + roles: ['data_analyst'], + full_name: 'test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login('analyst', 'analyst-password', { + expectSpaceSelector: false, + }); + }); + + after(async () => { + await security.role.delete('data_analyst'); + await security.user.delete('analyst'); + + await esArchiver.unload('dashboard/session_in_space'); + await PageObjects.security.forceLogout(); + }); + + it("Doesn't allow to store a session", async () => { + await PageObjects.common.navigateToApp('dashboard', { basePath: 's/another-space' }); + await PageObjects.dashboard.loadSavedDashboard('A Dashboard in another space'); + + await PageObjects.timePicker.setAbsoluteRange( + 'Sep 1, 2015 @ 00:00:00.000', + 'Oct 1, 2015 @ 00:00:00.000' + ); + + await PageObjects.dashboard.waitForRenderComplete(); + + await searchSessions.expectState('completed'); + await searchSessions.disabledOrFail(); + }); + }); }); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts index 6384afb179593..733b2edd4cd06 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const searchSessions = getService('searchSessions'); describe('discover in space', () => { - describe('Send to background in space', () => { + describe('Storing search sessions in space', () => { before(async () => { await esArchiver.load('dashboard/session_in_space'); @@ -93,7 +93,62 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Check that session is restored await searchSessions.expectState('restored'); - await testSubjects.missingOrFail('embeddableErrorLabel'); + await testSubjects.missingOrFail('discoverNoResultsError'); // expect error because of fake searchSessionId + }); + }); + describe('Disabled storing search sessions in space', () => { + before(async () => { + await esArchiver.load('dashboard/session_in_space'); + + await security.role.create('data_analyst', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['all'] }], + }, + kibana: [ + { + feature: { + discover: ['read'], + }, + spaces: ['another-space'], + }, + ], + }); + + await security.user.create('analyst', { + password: 'analyst-password', + roles: ['data_analyst'], + full_name: 'test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login('analyst', 'analyst-password', { + expectSpaceSelector: false, + }); + }); + + after(async () => { + await security.role.delete('data_analyst'); + await security.user.delete('analyst'); + + await esArchiver.unload('dashboard/session_in_space'); + await PageObjects.security.forceLogout(); + }); + + it("Doesn't allow to store a session", async () => { + await PageObjects.common.navigateToApp('discover', { basePath: 's/another-space' }); + + await PageObjects.discover.selectIndexPattern('logstash-*'); + + await PageObjects.timePicker.setAbsoluteRange( + 'Sep 1, 2015 @ 00:00:00.000', + 'Oct 1, 2015 @ 00:00:00.000' + ); + + await PageObjects.discover.waitForDocTableLoadingComplete(); + + await searchSessions.expectState('completed'); + await searchSessions.disabledOrFail(); }); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/lens/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/lens/index.ts new file mode 100644 index 0000000000000..e84cede4bc87d --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/lens/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + + describe('lens search sessions', function () { + this.tags('ciGroup3'); + + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + }); + + loadTestFile(require.resolve('./search_sessions.ts')); + }); +} diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/lens/search_sessions.ts b/x-pack/test/send_search_to_background_integration/tests/apps/lens/search_sessions.ts new file mode 100644 index 0000000000000..6bd79f0f98883 --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/lens/search_sessions.ts @@ -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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const searchSession = getService('searchSessions'); + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'timePicker', 'header']); + const listingTable = getService('listingTable'); + + describe('lens search sessions', () => { + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('lens/basic'); + }); + after(async () => { + await esArchiver.unload('lens/basic'); + }); + + it("doesn't shows search sessions indicator UI", async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsXYvis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.lens.isShowingNoResults()).to.be(false); + + await searchSession.missingOrFail(); + }); + }); +}