From b00deeb7543512c7af59619ad07a00776d58e274 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Tue, 23 Jun 2020 06:55:03 -0400 Subject: [PATCH 01/36] [IM] Move template wizard steps to shared directory + update legacy details panel (#69559) --- .../helpers/test_subjects.ts | 6 +-- .../home/index_templates_tab.test.ts | 12 +++--- .../components/shared/components/index.ts | 2 + .../shared/components/wizard_steps/index.ts | 9 ++++ .../components/wizard_steps}/step_aliases.tsx | 22 +++++----- .../wizard_steps}/step_mappings.tsx | 16 ++++---- .../wizard_steps}/step_settings.tsx | 20 ++++----- .../components/wizard_steps}/use_json_step.ts | 2 +- .../application/components/shared/index.ts | 9 +++- .../steps/step_aliases_container.tsx | 11 ++++- .../steps/step_mappings_container.tsx | 4 +- .../steps/step_settings_container.tsx | 11 ++++- .../template_details/template_details.tsx | 29 +++++++------ .../template_details/tabs/index.ts | 3 -- .../template_details/tabs/tab_aliases.tsx | 41 ------------------- .../template_details/tabs/tab_mappings.tsx | 41 ------------------- .../template_details/tabs/tab_settings.tsx | 41 ------------------- .../application/services/documentation.ts | 4 ++ .../translations/translations/ja-JP.json | 18 -------- .../translations/translations/zh-CN.json | 18 -------- 20 files changed, 99 insertions(+), 220 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/step_aliases.tsx (80%) rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/step_mappings.tsx (84%) rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/step_settings.tsx (81%) rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/use_json_step.ts (96%) delete mode 100644 x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx delete mode 100644 x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx delete mode 100644 x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 4e297118b0fdd..9889ebe16ba1e 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -5,7 +5,7 @@ */ export type TestSubjects = - | 'aliasesTab' + | 'aliasesTabContent' | 'appTitle' | 'cell' | 'closeDetailsButton' @@ -27,7 +27,7 @@ export type TestSubjects = | 'indicesTab' | 'legacyTemplateTable' | 'manageTemplateButton' - | 'mappingsTab' + | 'mappingsTabContent' | 'noAliasesCallout' | 'noMappingsCallout' | 'noSettingsCallout' @@ -36,7 +36,7 @@ export type TestSubjects = | 'row' | 'sectionError' | 'sectionLoading' - | 'settingsTab' + | 'settingsTabContent' | 'summaryTab' | 'summaryTitle' | 'systemTemplatesSwitch' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts index 7c79c7e61174e..2ff3743cd866c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts @@ -432,18 +432,18 @@ describe('Index Templates tab', () => { // Navigate and verify all tabs actions.selectDetailsTab('settings'); expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(true); + expect(exists('settingsTabContent')).toBe(true); actions.selectDetailsTab('aliases'); expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(false); - expect(exists('aliasesTab')).toBe(true); + expect(exists('settingsTabContent')).toBe(false); + expect(exists('aliasesTabContent')).toBe(true); actions.selectDetailsTab('mappings'); expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(false); - expect(exists('aliasesTab')).toBe(false); - expect(exists('mappingsTab')).toBe(true); + expect(exists('settingsTabContent')).toBe(false); + expect(exists('aliasesTabContent')).toBe(false); + expect(exists('mappingsTabContent')).toBe(true); }); test('should show an info callout if data is not present', async () => { diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts index 90d66bd1a5da4..e1700ad6a632d 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts +++ b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts @@ -5,3 +5,5 @@ */ export { TabAliases, TabMappings, TabSettings } from './details_panel'; + +export { StepAliases, StepMappings, StepSettings } from './wizard_steps'; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts new file mode 100644 index 0000000000000..90ce6227c09c8 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { StepAliases } from './step_aliases'; +export { StepMappings } from './step_mappings'; +export { StepSettings } from './step_settings'; diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases.tsx similarity index 80% rename from x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases.tsx index e18846a69b847..0d28ec4b50c9a 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases.tsx @@ -19,17 +19,17 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Forms } from '../../../../shared_imports'; -import { documentationService } from '../../../services/documentation'; +import { Forms } from '../../../../../shared_imports'; import { useJsonStep } from './use_json_step'; interface Props { defaultValue: { [key: string]: any }; onChange: (content: Forms.Content) => void; + esDocsBase: string; } export const StepAliases: React.FunctionComponent = React.memo( - ({ defaultValue, onChange }) => { + ({ defaultValue, onChange, esDocsBase }) => { const { jsonContent, setJsonContent, error } = useJsonStep({ defaultValue, onChange, @@ -42,7 +42,7 @@ export const StepAliases: React.FunctionComponent = React.memo(

@@ -53,7 +53,7 @@ export const StepAliases: React.FunctionComponent = React.memo(

@@ -64,13 +64,13 @@ export const StepAliases: React.FunctionComponent = React.memo( @@ -82,13 +82,13 @@ export const StepAliases: React.FunctionComponent = React.memo( } helpText={ = React.memo( showGutter={false} minLines={6} aria-label={i18n.translate( - 'xpack.idxMgmt.templateForm.stepAliases.fieldAliasesAriaLabel', + 'xpack.idxMgmt.formWizard.stepAliases.fieldAliasesAriaLabel', { defaultMessage: 'Aliases code editor', } diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx similarity index 84% rename from x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx index 800cb519a9393..2b9b689e17cb9 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx @@ -15,23 +15,23 @@ import { EuiText, } from '@elastic/eui'; -import { Forms } from '../../../../shared_imports'; -import { documentationService } from '../../../services/documentation'; +import { Forms } from '../../../../../shared_imports'; import { MappingsEditor, OnUpdateHandler, LoadMappingsFromJsonButton, IndexSettings, -} from '../../mappings_editor'; +} from '../../../mappings_editor'; interface Props { defaultValue: { [key: string]: any }; onChange: (content: Forms.Content) => void; indexSettings?: IndexSettings; + esDocsBase: string; } export const StepMappings: React.FunctionComponent = React.memo( - ({ defaultValue, onChange, indexSettings }) => { + ({ defaultValue, onChange, indexSettings, esDocsBase }) => { const [mappings, setMappings] = useState(defaultValue); const onMappingsEditorUpdate = useCallback( @@ -58,7 +58,7 @@ export const StepMappings: React.FunctionComponent = React.memo(

@@ -69,7 +69,7 @@ export const StepMappings: React.FunctionComponent = React.memo(

@@ -86,12 +86,12 @@ export const StepMappings: React.FunctionComponent = React.memo( diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx similarity index 81% rename from x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx index 4325852d68aaa..4eafcee0ef519 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx @@ -19,17 +19,17 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Forms } from '../../../../shared_imports'; -import { documentationService } from '../../../services/documentation'; +import { Forms } from '../../../../../shared_imports'; import { useJsonStep } from './use_json_step'; interface Props { defaultValue: { [key: string]: any }; onChange: (content: Forms.Content) => void; + esDocsBase: string; } export const StepSettings: React.FunctionComponent = React.memo( - ({ defaultValue, onChange }) => { + ({ defaultValue, onChange, esDocsBase }) => { const { jsonContent, setJsonContent, error } = useJsonStep({ defaultValue, onChange, @@ -42,7 +42,7 @@ export const StepSettings: React.FunctionComponent = React.memo(

@@ -53,7 +53,7 @@ export const StepSettings: React.FunctionComponent = React.memo(

@@ -64,12 +64,12 @@ export const StepSettings: React.FunctionComponent = React.memo( @@ -82,13 +82,13 @@ export const StepSettings: React.FunctionComponent = React.memo( } helpText={ {JSON.stringify({ number_of_replicas: 1 })}, @@ -114,7 +114,7 @@ export const StepSettings: React.FunctionComponent = React.memo( showGutter={false} minLines={6} aria-label={i18n.translate( - 'xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsAriaLabel', + 'xpack.idxMgmt.formWizard.stepSettings.fieldIndexSettingsAriaLabel', { defaultMessage: 'Index settings editor', } diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/use_json_step.ts similarity index 96% rename from x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/use_json_step.ts index 4c1b36e3abba5..67799f1e76d85 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/use_json_step.ts @@ -7,7 +7,7 @@ import { useEffect, useState, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; -import { isJSON, Forms } from '../../../../shared_imports'; +import { isJSON, Forms } from '../../../../../shared_imports'; interface Parameters { onChange: (content: Forms.Content) => void; diff --git a/x-pack/plugins/index_management/public/application/components/shared/index.ts b/x-pack/plugins/index_management/public/application/components/shared/index.ts index e015ef72e244f..5ec1f71710270 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/index.ts +++ b/x-pack/plugins/index_management/public/application/components/shared/index.ts @@ -4,4 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -export { TabAliases, TabMappings, TabSettings } from './components'; +export { + TabAliases, + TabMappings, + TabSettings, + StepAliases, + StepMappings, + StepSettings, +} from './components'; diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx index 634887436f816..a0e0c59be6622 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx @@ -6,11 +6,18 @@ import React from 'react'; import { Forms } from '../../../../shared_imports'; +import { documentationService } from '../../../services/documentation'; +import { StepAliases } from '../../shared'; import { WizardContent } from '../template_form'; -import { StepAliases } from './step_aliases'; export const StepAliasesContainer = () => { const { defaultValue, updateContent } = Forms.useContent('aliases'); - return ; + return ( + + ); }; diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx index 545aec9851592..80c0d1d4df489 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx @@ -6,8 +6,9 @@ import React from 'react'; import { Forms } from '../../../../shared_imports'; +import { documentationService } from '../../../services/documentation'; +import { StepMappings } from '../../shared'; import { WizardContent } from '../template_form'; -import { StepMappings } from './step_mappings'; export const StepMappingsContainer = () => { const { defaultValue, updateContent, getData } = Forms.useContent('mappings'); @@ -17,6 +18,7 @@ export const StepMappingsContainer = () => { defaultValue={defaultValue} onChange={updateContent} indexSettings={getData().settings} + esDocsBase={documentationService.getEsDocsBase()} /> ); }; diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx index 4d7de644a1442..b79c6804d382b 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx @@ -6,11 +6,18 @@ import React from 'react'; import { Forms } from '../../../../shared_imports'; +import { documentationService } from '../../../services/documentation'; +import { StepSettings } from '../../shared'; import { WizardContent } from '../template_form'; -import { StepSettings } from './step_settings'; export const StepSettingsContainer = React.memo(() => { const { defaultValue, updateContent } = Forms.useContent('settings'); - return ; + return ( + + ); }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx index 807229fb36267..ab4ce6a61a9b6 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx @@ -30,7 +30,6 @@ import { UIM_TEMPLATE_DETAIL_PANEL_SETTINGS_TAB, UIM_TEMPLATE_DETAIL_PANEL_ALIASES_TAB, } from '../../../../../../../common/constants'; -import { TemplateDeserialized } from '../../../../../../../common'; import { TemplateDeleteModal, SectionLoading, @@ -41,7 +40,8 @@ import { useLoadIndexTemplate } from '../../../../../services/api'; import { decodePathFromReactRouter } from '../../../../../services/routing'; import { SendRequestResponse } from '../../../../../../shared_imports'; import { useServices } from '../../../../../app_context'; -import { TabSummary, TabMappings, TabSettings, TabAliases } from '../../template_details/tabs'; +import { TabAliases, TabMappings, TabSettings } from '../../../../../components/shared'; +import { TabSummary } from '../../template_details/tabs'; interface Props { template: { name: string; isLegacy?: boolean }; @@ -83,15 +83,6 @@ const TABS = [ }, ]; -const tabToComponentMap: { - [key: string]: React.FunctionComponent<{ templateDetails: TemplateDeserialized }>; -} = { - [SUMMARY_TAB_ID]: TabSummary, - [SETTINGS_TAB_ID]: TabSettings, - [MAPPINGS_TAB_ID]: TabMappings, - [ALIASES_TAB_ID]: TabAliases, -}; - const tabToUiMetricMap: { [key: string]: string } = { [SUMMARY_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_SUMMARY_TAB, [SETTINGS_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_SETTINGS_TAB, @@ -144,7 +135,19 @@ export const LegacyTemplateDetails: React.FunctionComponent = ({ /> ); } else if (templateDetails) { - const Content = tabToComponentMap[activeTab]; + const { + template: { settings, mappings, aliases }, + } = templateDetails; + + const tabToComponentMap: Record = { + [SUMMARY_TAB_ID]: , + [SETTINGS_TAB_ID]: , + [MAPPINGS_TAB_ID]: , + [ALIASES_TAB_ID]: , + }; + + const tabContent = tabToComponentMap[activeTab]; + const managedTemplateCallout = isManaged ? ( = ({ - + {tabContent} ); } diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/index.ts b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/index.ts index 7af28f4688f48..08ebda2b5e437 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/index.ts +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/index.ts @@ -5,6 +5,3 @@ */ export { TabSummary } from './tab_summary'; -export { TabMappings } from './tab_mappings'; -export { TabSettings } from './tab_settings'; -export { TabAliases } from './tab_aliases'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx deleted file mode 100644 index fa7d734ad0d2b..0000000000000 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx +++ /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 React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCodeBlock, EuiCallOut } from '@elastic/eui'; -import { TemplateDeserialized } from '../../../../../../../common'; - -interface Props { - templateDetails: TemplateDeserialized; -} - -export const TabAliases: React.FunctionComponent = ({ templateDetails }) => { - const { - template: { aliases }, - } = templateDetails; - - if (aliases && Object.keys(aliases).length) { - return ( -
- {JSON.stringify(aliases, null, 2)} -
- ); - } - - return ( - - } - iconType="pin" - data-test-subj="noAliasesCallout" - /> - ); -}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx deleted file mode 100644 index 6e0257c6b377b..0000000000000 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx +++ /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 React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCodeBlock, EuiCallOut } from '@elastic/eui'; -import { TemplateDeserialized } from '../../../../../../../common'; - -interface Props { - templateDetails: TemplateDeserialized; -} - -export const TabMappings: React.FunctionComponent = ({ templateDetails }) => { - const { - template: { mappings }, - } = templateDetails; - - if (mappings && Object.keys(mappings).length) { - return ( -
- {JSON.stringify(mappings, null, 2)} -
- ); - } - - return ( - - } - iconType="pin" - data-test-subj="noMappingsCallout" - /> - ); -}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx deleted file mode 100644 index 8f75c2cb77801..0000000000000 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx +++ /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 React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCodeBlock, EuiCallOut } from '@elastic/eui'; -import { TemplateDeserialized } from '../../../../../../../common'; - -interface Props { - templateDetails: TemplateDeserialized; -} - -export const TabSettings: React.FunctionComponent = ({ templateDetails }) => { - const { - template: { settings }, - } = templateDetails; - - if (settings && Object.keys(settings).length) { - return ( -
- {JSON.stringify(settings, null, 2)} -
- ); - } - - return ( - - } - iconType="pin" - data-test-subj="noSettingsCallout" - /> - ); -}; diff --git a/x-pack/plugins/index_management/public/application/services/documentation.ts b/x-pack/plugins/index_management/public/application/services/documentation.ts index fdf07c43a0c8b..ccccccce19766 100644 --- a/x-pack/plugins/index_management/public/application/services/documentation.ts +++ b/x-pack/plugins/index_management/public/application/services/documentation.ts @@ -20,6 +20,10 @@ class DocumentationService { this.kibanaDocsBase = `${docsBase}/kibana/${DOC_LINK_VERSION}`; } + public getEsDocsBase() { + return this.esDocsBase; + } + public getSettingsDocumentationLink() { return `${this.esDocsBase}/index-modules.html#index-modules-settings`; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fb8a4c3464d11..0567ee675ee75 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7065,9 +7065,6 @@ "xpack.idxMgmt.summary.summaryTitle": "一般", "xpack.idxMgmt.templateCreate.loadingTemplateToCloneDescription": "クローンを作成するテンプレートを読み込み中…", "xpack.idxMgmt.templateCreate.loadingTemplateToCloneErrorMessage": "クローンを作成するテンプレートを読み込み中にエラーが発生", - "xpack.idxMgmt.templateDetails.aliasesTab.noAliasesTitle": "エイリアスが定義されていません。", - "xpack.idxMgmt.templateDetails.mappingsTab.noMappingsTitle": "マッピングが定義されていません。", - "xpack.idxMgmt.templateDetails.settingsTab.noSettingsTitle": "設定が定義されていません。", "xpack.idxMgmt.templateDetails.summaryTab.ilmPolicyDescriptionListTitle": "ILM ポリシー", "xpack.idxMgmt.templateDetails.summaryTab.indexPatternsDescriptionListTitle": "インデックス{numIndexPatterns, plural, one {パターン} other {パターン}}", "xpack.idxMgmt.templateDetails.summaryTab.noneDescriptionText": "なし", @@ -7082,12 +7079,6 @@ "xpack.idxMgmt.templateForm.createButtonLabel": "テンプレートを作成", "xpack.idxMgmt.templateForm.saveButtonLabel": "テンプレートを保存", "xpack.idxMgmt.templateForm.saveTemplateError": "テンプレートを作成できません", - "xpack.idxMgmt.templateForm.stepAliases.aliasesDescription": "エイリアスをセットアップして、インデックスに関連付けてください。", - "xpack.idxMgmt.templateForm.stepAliases.aliasesEditorHelpText": "JSON フォーマットを使用: {code}", - "xpack.idxMgmt.templateForm.stepAliases.docsButtonLabel": "インデックステンプレートドキュメント", - "xpack.idxMgmt.templateForm.stepAliases.fieldAliasesAriaLabel": "エイリアスコードエディター", - "xpack.idxMgmt.templateForm.stepAliases.fieldAliasesLabel": "エイリアス", - "xpack.idxMgmt.templateForm.stepAliases.stepTitle": "エイリアス (任意)", "xpack.idxMgmt.templateForm.stepLogistics.docsButtonLabel": "インデックステンプレートドキュメント", "xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsHelpText": "スペースと {invalidCharactersList} は使用できません。", "xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsLabel": "インデックスパターン", @@ -7103,9 +7094,6 @@ "xpack.idxMgmt.templateForm.stepLogistics.stepTitle": "ロジスティクス", "xpack.idxMgmt.templateForm.stepLogistics.versionDescription": "テンプレートを外部管理システムで識別するための番号です。", "xpack.idxMgmt.templateForm.stepLogistics.versionTitle": "バージョン", - "xpack.idxMgmt.templateForm.stepMappings.docsButtonLabel": "マッピングドキュメント", - "xpack.idxMgmt.templateForm.stepMappings.mappingsDescription": "ドキュメントの保存とインデックス方法を定義します。", - "xpack.idxMgmt.templateForm.stepMappings.stepTitle": "マッピング (任意)", "xpack.idxMgmt.templateForm.stepReview.requestTab.descriptionText": "このリクエストは次のインデックステンプレートを作成します。", "xpack.idxMgmt.templateForm.stepReview.requestTabTitle": "リクエスト", "xpack.idxMgmt.templateForm.stepReview.stepTitle": "「{templateName}」の詳細の確認", @@ -7127,12 +7115,6 @@ "xpack.idxMgmt.templateForm.steps.mappingsStepName": "マッピング", "xpack.idxMgmt.templateForm.steps.settingsStepName": "インデックス設定", "xpack.idxMgmt.templateForm.steps.summaryStepName": "テンプレートのレビュー", - "xpack.idxMgmt.templateForm.stepSettings.docsButtonLabel": "インデックス設定ドキュメント", - "xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsAriaLabel": "インデックス設定エディター", - "xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsLabel": "インデックス設定", - "xpack.idxMgmt.templateForm.stepSettings.settingsDescription": "インデックスの動作を定義します。", - "xpack.idxMgmt.templateForm.stepSettings.settingsEditorHelpText": "JSON フォーマットを使用: {code}", - "xpack.idxMgmt.templateForm.stepSettings.stepTitle": "インデックス設定 (任意)", "xpack.idxMgmt.templateList.table.ilmPolicyColumnDescription": "インデックスライフサイクルポリシー「{policyName}」", "xpack.idxMgmt.templateList.table.ilmPolicyColumnTitle": "ILM ポリシー", "xpack.idxMgmt.templateList.table.indexPatternsColumnTitle": "インデックスパターン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3fe58b62c00c3..86f2c44c809da 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7069,9 +7069,6 @@ "xpack.idxMgmt.summary.summaryTitle": "常规", "xpack.idxMgmt.templateCreate.loadingTemplateToCloneDescription": "正在加载要克隆的模板……", "xpack.idxMgmt.templateCreate.loadingTemplateToCloneErrorMessage": "加载要克隆的模板时出错", - "xpack.idxMgmt.templateDetails.aliasesTab.noAliasesTitle": "未定义任何别名。", - "xpack.idxMgmt.templateDetails.mappingsTab.noMappingsTitle": "未定义任何映射。", - "xpack.idxMgmt.templateDetails.settingsTab.noSettingsTitle": "未定义任何设置。", "xpack.idxMgmt.templateDetails.summaryTab.ilmPolicyDescriptionListTitle": "ILM 策略", "xpack.idxMgmt.templateDetails.summaryTab.indexPatternsDescriptionListTitle": "索引{numIndexPatterns, plural, one {模式} other {模式}}", "xpack.idxMgmt.templateDetails.summaryTab.noneDescriptionText": "无", @@ -7086,12 +7083,6 @@ "xpack.idxMgmt.templateForm.createButtonLabel": "创建模板", "xpack.idxMgmt.templateForm.saveButtonLabel": "保存模板", "xpack.idxMgmt.templateForm.saveTemplateError": "无法创建模板", - "xpack.idxMgmt.templateForm.stepAliases.aliasesDescription": "设置要与索引关联的别名。", - "xpack.idxMgmt.templateForm.stepAliases.aliasesEditorHelpText": "使用 JSON 格式:{code}", - "xpack.idxMgmt.templateForm.stepAliases.docsButtonLabel": "索引模板文档", - "xpack.idxMgmt.templateForm.stepAliases.fieldAliasesAriaLabel": "别名代码编辑器", - "xpack.idxMgmt.templateForm.stepAliases.fieldAliasesLabel": "别名", - "xpack.idxMgmt.templateForm.stepAliases.stepTitle": "别名(可选)", "xpack.idxMgmt.templateForm.stepLogistics.docsButtonLabel": "索引模板文档", "xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsHelpText": "不允许使用空格和字符 {invalidCharactersList}。", "xpack.idxMgmt.templateForm.stepLogistics.fieldIndexPatternsLabel": "索引模式", @@ -7107,9 +7098,6 @@ "xpack.idxMgmt.templateForm.stepLogistics.stepTitle": "运筹", "xpack.idxMgmt.templateForm.stepLogistics.versionDescription": "在外部管理系统中标识该模板的编号。", "xpack.idxMgmt.templateForm.stepLogistics.versionTitle": "版本", - "xpack.idxMgmt.templateForm.stepMappings.docsButtonLabel": "映射文档", - "xpack.idxMgmt.templateForm.stepMappings.mappingsDescription": "定义如何存储和索引文档。", - "xpack.idxMgmt.templateForm.stepMappings.stepTitle": "映射(可选)", "xpack.idxMgmt.templateForm.stepReview.requestTab.descriptionText": "此请求将创建以下索引模板。", "xpack.idxMgmt.templateForm.stepReview.requestTabTitle": "请求", "xpack.idxMgmt.templateForm.stepReview.stepTitle": "查看 “{templateName}” 的详情", @@ -7131,12 +7119,6 @@ "xpack.idxMgmt.templateForm.steps.mappingsStepName": "映射", "xpack.idxMgmt.templateForm.steps.settingsStepName": "索引设置", "xpack.idxMgmt.templateForm.steps.summaryStepName": "复查模板", - "xpack.idxMgmt.templateForm.stepSettings.docsButtonLabel": "索引设置文档", - "xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsAriaLabel": "索引设置编辑器", - "xpack.idxMgmt.templateForm.stepSettings.fieldIndexSettingsLabel": "索引设置", - "xpack.idxMgmt.templateForm.stepSettings.settingsDescription": "定义索引的行为。", - "xpack.idxMgmt.templateForm.stepSettings.settingsEditorHelpText": "使用 JSON 格式:{code}", - "xpack.idxMgmt.templateForm.stepSettings.stepTitle": "索引设置(可选)", "xpack.idxMgmt.templateList.table.ilmPolicyColumnDescription": "“{policyName}”索引生命周期策略", "xpack.idxMgmt.templateList.table.ilmPolicyColumnTitle": "ILM 策略", "xpack.idxMgmt.templateList.table.indexPatternsColumnTitle": "索引模式", From 6cc8ea1861fa087199193b337db03c9144fc7687 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 23 Jun 2020 14:38:39 +0300 Subject: [PATCH 02/36] Migrate dashboard mode (#69305) Closes: #67469 Co-authored-by: Elastic Machine --- x-pack/.i18nrc.json | 2 +- x-pack/index.js | 4 +- x-pack/legacy/plugins/dashboard_mode/index.js | 64 ------- .../dashboard_mode_request_interceptor.js | 163 ------------------ .../dashboard_mode_request_interceptor.js | 73 -------- x-pack/legacy/plugins/ingest_manager/index.ts | 1 + .../dashboard_mode/common/constants.ts} | 4 +- .../dashboard_mode/common/index.ts} | 2 +- x-pack/plugins/dashboard_mode/kibana.json | 7 +- x-pack/plugins/dashboard_mode/server/index.ts | 14 +- ...dashboard_mode_request_interceptor.test.ts | 111 ++++++++++++ .../dashboard_mode_request_interceptor.ts | 79 +++++++++ .../server/interceptors/index.ts} | 2 +- .../plugins/dashboard_mode/server/plugin.ts | 62 +++++++ .../dashboard_mode/server/ui_settings.ts | 34 ++++ .../plugins/ingest_manager/server/plugin.ts | 2 +- .../test/api_integration/apis/fleet/index.js | 2 +- x-pack/test/api_integration/apis/index.js | 4 +- 18 files changed, 311 insertions(+), 319 deletions(-) delete mode 100644 x-pack/legacy/plugins/dashboard_mode/index.js delete mode 100644 x-pack/legacy/plugins/dashboard_mode/server/__tests__/dashboard_mode_request_interceptor.js delete mode 100644 x-pack/legacy/plugins/dashboard_mode/server/dashboard_mode_request_interceptor.js rename x-pack/{legacy/plugins/dashboard_mode/server/index.js => plugins/dashboard_mode/common/constants.ts} (71%) rename x-pack/{legacy/plugins/dashboard_mode/common/index.js => plugins/dashboard_mode/common/index.ts} (84%) create mode 100644 x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts create mode 100644 x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts rename x-pack/{legacy/plugins/dashboard_mode/common/constants.js => plugins/dashboard_mode/server/interceptors/index.ts} (72%) create mode 100644 x-pack/plugins/dashboard_mode/server/plugin.ts create mode 100644 x-pack/plugins/dashboard_mode/server/ui_settings.ts diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 278968cb47231..36cfdf904d6d4 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -11,7 +11,7 @@ "xpack.dashboard": "plugins/dashboard_enhanced", "xpack.discover": "plugins/discover_enhanced", "xpack.crossClusterReplication": "plugins/cross_cluster_replication", - "xpack.dashboardMode": "legacy/plugins/dashboard_mode", + "xpack.dashboardMode": "plugins/dashboard_mode", "xpack.data": "plugins/data_enhanced", "xpack.embeddableEnhanced": "plugins/embeddable_enhanced", "xpack.endpoint": "plugins/endpoint", diff --git a/x-pack/index.js b/x-pack/index.js index e7dd4886e6052..2d2e42650cfa7 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -7,7 +7,6 @@ import { xpackMain } from './legacy/plugins/xpack_main'; import { monitoring } from './legacy/plugins/monitoring'; import { security } from './legacy/plugins/security'; -import { dashboardMode } from './legacy/plugins/dashboard_mode'; import { beats } from './legacy/plugins/beats_management'; import { spaces } from './legacy/plugins/spaces'; import { ingestManager } from './legacy/plugins/ingest_manager'; @@ -18,8 +17,7 @@ module.exports = function (kibana) { monitoring(kibana), spaces(kibana), security(kibana), - dashboardMode(kibana), - beats(kibana), ingestManager(kibana), + beats(kibana), ]; }; diff --git a/x-pack/legacy/plugins/dashboard_mode/index.js b/x-pack/legacy/plugins/dashboard_mode/index.js deleted file mode 100644 index 1145fad2a8a28..0000000000000 --- a/x-pack/legacy/plugins/dashboard_mode/index.js +++ /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 { resolve } from 'path'; -import { i18n } from '@kbn/i18n'; -import { CONFIG_DASHBOARD_ONLY_MODE_ROLES } from './common'; -import { createDashboardModeRequestInterceptor } from './server'; - -// Copied largely from plugins/kibana/index.js. The dashboard viewer includes just the dashboard section of -// the standard kibana plugin. We don't want to include code for the other links (visualize, dev tools, etc) -// since it's view only, but we want the urls to be the same, so we are using largely the same setup. -export function dashboardMode(kibana) { - return new kibana.Plugin({ - id: 'dashboard_mode', - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main'], - uiExports: { - uiSettingDefaults: { - [CONFIG_DASHBOARD_ONLY_MODE_ROLES]: { - name: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle', { - defaultMessage: 'Dashboards only roles', - }), - description: i18n.translate( - 'xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription', - { - defaultMessage: 'Roles that belong to View Dashboards Only mode', - } - ), - value: ['kibana_dashboard_only_user'], - category: ['dashboard'], - deprecation: { - message: i18n.translate( - 'xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation', - { - defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.', - } - ), - docLinksKey: 'dashboardSettings', - }, - }, - }, - }, - - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - - init(server) { - server.injectUiAppVars( - 'dashboardViewer', - async () => await server.getInjectedUiAppVars('kibana') - ); - - if (server.plugins.security) { - server.ext(createDashboardModeRequestInterceptor()); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/dashboard_mode/server/__tests__/dashboard_mode_request_interceptor.js b/x-pack/legacy/plugins/dashboard_mode/server/__tests__/dashboard_mode_request_interceptor.js deleted file mode 100644 index 3fd9bf5f59d52..0000000000000 --- a/x-pack/legacy/plugins/dashboard_mode/server/__tests__/dashboard_mode_request_interceptor.js +++ /dev/null @@ -1,163 +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 Hapi from 'hapi'; - -import { createDashboardModeRequestInterceptor } from '../dashboard_mode_request_interceptor'; - -const DASHBOARD_ONLY_MODE_ROLE = 'test_dashboard_only_mode_role'; - -function setup() { - const server = new Hapi.Server(); - - server.decorate('request', 'getUiSettingsService', () => { - return { - get: () => Promise.resolve([DASHBOARD_ONLY_MODE_ROLE]), - }; - }); - - // attach the extension - server.ext(createDashboardModeRequestInterceptor()); - - // allow the extension to fake "render an app" - server.decorate('toolkit', 'renderApp', function (app) { - // `this` is the `h` response toolkit - return this.response({ renderApp: true, app }); - }); - - server.decorate('server', 'newPlatform', { - setup: { - core: { - http: { - basePath: { - get: () => '', - }, - }, - }, - }, - }); - - server.route({ - path: '/app/{appId}', - method: 'GET', - handler(req, h) { - return h.renderApp({ name: req.params.appId }); - }, - }); - - // catch all route for determining when we get through the extensions - server.route({ - path: '/{path*}', - method: 'GET', - handler(req) { - return { catchAll: true, path: `/${req.params.path}` }; - }, - }); - - return { server }; -} - -describe('DashboardOnlyModeRequestInterceptor', () => { - describe('request is not for dashboad-only user', () => { - describe('app route', () => { - it('lets the route render as normal', async () => { - const { server } = setup(); - const response = await server.inject({ - url: '/app/kibana', - credentials: { - roles: ['foo', 'bar'], - }, - }); - - expect(response) - .to.have.property('statusCode', 200) - .and.have.property('result') - .eql({ - renderApp: true, - app: { name: 'kibana' }, - }); - }); - }); - - describe('non-app route', () => { - it('lets the route render as normal', async () => { - const { server } = setup(); - const response = await server.inject({ - url: '/foo/bar', - credentials: { - roles: ['foo', 'bar'], - }, - }); - - expect(response).to.have.property('statusCode', 200).and.have.property('result').eql({ - catchAll: true, - path: '/foo/bar', - }); - }); - }); - }); - - describe('request for dashboard-only user', () => { - describe('non-kibana app route', () => { - it('responds with 404', async () => { - const { server } = setup(); - const response = await server.inject({ - url: '/app/foo', - credentials: { - roles: [DASHBOARD_ONLY_MODE_ROLE], - }, - }); - - expect(response).to.have.property('statusCode', 404); - }); - }); - - describe('requests to dashboard_mode app', () => { - it('lets the route render as normal', async () => { - const { server } = setup(); - const response = await server.inject({ - url: '/app/dashboard_mode', - credentials: { - roles: [DASHBOARD_ONLY_MODE_ROLE], - }, - }); - - expect(response) - .to.have.property('statusCode', 200) - .and.have.property('result') - .eql({ - renderApp: true, - app: { name: 'dashboard_mode' }, - }); - }); - }); - - function testRedirectToDashboardModeApp(url) { - describe(`requests to url:"${url}"`, () => { - it('redirects to the dashboard_mode app instead', async () => { - const { server } = setup(); - const response = await server.inject({ - url: url, - credentials: { - roles: [DASHBOARD_ONLY_MODE_ROLE], - }, - }); - - expect(response).to.have.property('statusCode', 301); - expect(response.headers).to.have.property('location', '/app/dashboard_mode'); - }); - }); - } - - testRedirectToDashboardModeApp('/app/kibana'); - testRedirectToDashboardModeApp('/app/kibana#/foo/bar'); - testRedirectToDashboardModeApp('/app/kibana/foo/bar'); - testRedirectToDashboardModeApp('/app/kibana?foo=bar'); - testRedirectToDashboardModeApp('/app/dashboards?foo=bar'); - testRedirectToDashboardModeApp('/app/home?foo=bar'); - }); -}); diff --git a/x-pack/legacy/plugins/dashboard_mode/server/dashboard_mode_request_interceptor.js b/x-pack/legacy/plugins/dashboard_mode/server/dashboard_mode_request_interceptor.js deleted file mode 100644 index 76a582d6cf239..0000000000000 --- a/x-pack/legacy/plugins/dashboard_mode/server/dashboard_mode_request_interceptor.js +++ /dev/null @@ -1,73 +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 Boom from 'boom'; - -import { CONFIG_DASHBOARD_ONLY_MODE_ROLES } from '../common'; - -const superuserRole = 'superuser'; - -/** - * Intercept all requests after auth has completed and apply filtering - * logic to enforce dashboard only mode. - * - * @type {Hapi.RequestExtension} - */ -export function createDashboardModeRequestInterceptor() { - return { - type: 'onPostAuth', - async method(request, h) { - const { auth, url } = request; - const user = auth.credentials; - const roles = user ? user.roles : []; - - if (!user) { - return h.continue; - } - - const isAppRequest = url.path.startsWith('/app/'); - - // The act of retrieving this setting ends up creating the config document if it doesn't already exist. - // Various functional tests have come to indirectly rely on this behavior, so changing this is non-trivial. - // This will be addressed once dashboard-only-mode is removed altogether. - const uiSettings = request.getUiSettingsService(); - const dashboardOnlyModeRoles = await uiSettings.get(CONFIG_DASHBOARD_ONLY_MODE_ROLES); - - if (!isAppRequest || !dashboardOnlyModeRoles || !roles || roles.length === 0) { - return h.continue; - } - - const isDashboardOnlyModeUser = user.roles.find((role) => - dashboardOnlyModeRoles.includes(role) - ); - const isSuperUser = user.roles.find((role) => role === superuserRole); - - const enforceDashboardOnlyMode = isDashboardOnlyModeUser && !isSuperUser; - if (enforceDashboardOnlyMode) { - if ( - url.path.startsWith('/app/home') || - url.path.startsWith('/app/kibana') || - url.path.startsWith('/app/dashboards') - ) { - const basePath = request.server.newPlatform.setup.core.http.basePath.get(request); - const url = `${basePath}/app/dashboard_mode`; - // If the user is in "Dashboard only mode" they should only be allowed to see - // the dashboard app and none others. If the kibana app is requested, this might be a old - // url we will migrate on the fly. - return h.redirect(url).permanent().takeover(); - } - if (url.path.startsWith('/app/dashboard_mode')) { - // let through requests to the dashboard_mode app - return h.continue; - } - - throw Boom.notFound(); - } - - return h.continue; - }, - }; -} diff --git a/x-pack/legacy/plugins/ingest_manager/index.ts b/x-pack/legacy/plugins/ingest_manager/index.ts index df9923d9f11ec..2b20bf16f2400 100644 --- a/x-pack/legacy/plugins/ingest_manager/index.ts +++ b/x-pack/legacy/plugins/ingest_manager/index.ts @@ -8,6 +8,7 @@ import { resolve } from 'path'; export function ingestManager(kibana: any) { return new kibana.Plugin({ id: 'ingestManager', + require: ['kibana', 'elasticsearch', 'xpack_main'], publicDir: resolve(__dirname, '../../../plugins/ingest_manager/public'), }); } diff --git a/x-pack/legacy/plugins/dashboard_mode/server/index.js b/x-pack/plugins/dashboard_mode/common/constants.ts similarity index 71% rename from x-pack/legacy/plugins/dashboard_mode/server/index.js rename to x-pack/plugins/dashboard_mode/common/constants.ts index a7193bb560ca1..f5d36fe9799c7 100644 --- a/x-pack/legacy/plugins/dashboard_mode/server/index.js +++ b/x-pack/plugins/dashboard_mode/common/constants.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createDashboardModeRequestInterceptor } from './dashboard_mode_request_interceptor'; +export const UI_SETTINGS = { + CONFIG_DASHBOARD_ONLY_MODE_ROLES: 'xpackDashboardMode:roles', +}; diff --git a/x-pack/legacy/plugins/dashboard_mode/common/index.js b/x-pack/plugins/dashboard_mode/common/index.ts similarity index 84% rename from x-pack/legacy/plugins/dashboard_mode/common/index.js rename to x-pack/plugins/dashboard_mode/common/index.ts index 358d0d5b7e076..60cf0060636d7 100644 --- a/x-pack/legacy/plugins/dashboard_mode/common/index.js +++ b/x-pack/plugins/dashboard_mode/common/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './constants'; +export { UI_SETTINGS } from './constants'; diff --git a/x-pack/plugins/dashboard_mode/kibana.json b/x-pack/plugins/dashboard_mode/kibana.json index dfe3221025092..4777b9b25be23 100644 --- a/x-pack/plugins/dashboard_mode/kibana.json +++ b/x-pack/plugins/dashboard_mode/kibana.json @@ -3,10 +3,13 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": [ - "xpack", "dashboard_mode" + "xpack", + "dashboard_mode" ], + "optionalPlugins": ["security"], "requiredPlugins": [ - "kibanaLegacy", "dashboard" + "kibanaLegacy", + "dashboard" ], "server": true, "ui": true diff --git a/x-pack/plugins/dashboard_mode/server/index.ts b/x-pack/plugins/dashboard_mode/server/index.ts index 2a8890c2f81ac..671a398734ac1 100644 --- a/x-pack/plugins/dashboard_mode/server/index.ts +++ b/x-pack/plugins/dashboard_mode/server/index.ts @@ -4,17 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginConfigDescriptor } from 'kibana/server'; - +import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server'; import { schema } from '@kbn/config-schema'; +import { DashboardModeServerPlugin } from './plugin'; + export const config: PluginConfigDescriptor = { schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), }; -export const plugin = () => ({ - setup() {}, - start() {}, -}); +export function plugin(initializerContext: PluginInitializerContext) { + return new DashboardModeServerPlugin(initializerContext); +} + +export { DashboardModeServerPlugin as Plugin }; diff --git a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts new file mode 100644 index 0000000000000..2978c48af7414 --- /dev/null +++ b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + OnPostAuthHandler, + OnPostAuthToolkit, + KibanaRequest, + LifecycleResponseFactory, + IUiSettingsClient, +} from 'kibana/server'; +import { coreMock } from '../../../../../src/core/server/mocks'; + +import { AuthenticatedUser } from '../../../security/server'; +import { securityMock } from '../../../security/server/mocks'; + +import { setupDashboardModeRequestInterceptor } from './dashboard_mode_request_interceptor'; + +const DASHBOARD_ONLY_MODE_ROLE = 'test_dashboard_only_mode_role'; + +describe('DashboardOnlyModeRequestInterceptor', () => { + const core = coreMock.createSetup(); + const security = securityMock.createSetup(); + + let interceptor: OnPostAuthHandler; + let toolkit: OnPostAuthToolkit; + let uiSettingsMock: any; + + beforeEach(() => { + toolkit = { + next: jest.fn(), + }; + interceptor = setupDashboardModeRequestInterceptor({ + http: core.http, + security, + getUiSettingsClient: () => + (Promise.resolve({ + get: () => Promise.resolve(uiSettingsMock), + }) as unknown) as Promise, + }); + }); + + test('should not redirects for not app/* requests', async () => { + const request = ({ + url: { + path: 'api/test', + }, + } as unknown) as KibanaRequest; + + interceptor(request, {} as LifecycleResponseFactory, toolkit); + + expect(toolkit.next).toHaveBeenCalled(); + }); + + test('should not redirects not authenticated users', async () => { + const request = ({ + url: { + path: '/app/home', + }, + } as unknown) as KibanaRequest; + + interceptor(request, {} as LifecycleResponseFactory, toolkit); + + expect(toolkit.next).toHaveBeenCalled(); + }); + + describe('request for dashboard-only user', () => { + function testRedirectToDashboardModeApp(url: string) { + describe(`requests to url:"${url}"`, () => { + test('redirects to the dashboard_mode app instead', async () => { + const request = ({ + url: { + path: url, + }, + credentials: { + roles: [DASHBOARD_ONLY_MODE_ROLE], + }, + } as unknown) as KibanaRequest; + + const response = ({ + redirected: jest.fn(), + } as unknown) as LifecycleResponseFactory; + + security.authc.getCurrentUser = jest.fn( + (r: KibanaRequest) => + ({ + roles: [DASHBOARD_ONLY_MODE_ROLE], + } as AuthenticatedUser) + ); + + uiSettingsMock = [DASHBOARD_ONLY_MODE_ROLE]; + + await interceptor(request, response, toolkit); + + expect(response.redirected).toHaveBeenCalledWith({ + headers: { location: `/mock-server-basepath/app/dashboard_mode` }, + }); + }); + }); + } + + testRedirectToDashboardModeApp('/app/kibana'); + testRedirectToDashboardModeApp('/app/kibana#/foo/bar'); + testRedirectToDashboardModeApp('/app/kibana/foo/bar'); + testRedirectToDashboardModeApp('/app/kibana?foo=bar'); + testRedirectToDashboardModeApp('/app/dashboards?foo=bar'); + testRedirectToDashboardModeApp('/app/home?foo=bar'); + }); +}); diff --git a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts new file mode 100644 index 0000000000000..4378c818f087c --- /dev/null +++ b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts @@ -0,0 +1,79 @@ +/* + * 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 { HttpServiceSetup, OnPostAuthHandler, IUiSettingsClient } from 'kibana/server'; +import { SecurityPluginSetup } from '../../../security/server'; +import { UI_SETTINGS } from '../../common'; + +const superuserRole = 'superuser'; + +interface DashboardModeRequestInterceptorDependencies { + http: HttpServiceSetup; + security: SecurityPluginSetup; + getUiSettingsClient: () => Promise; +} + +export const setupDashboardModeRequestInterceptor = ({ + http, + security, + getUiSettingsClient, +}: DashboardModeRequestInterceptorDependencies) => + (async (request, response, toolkit) => { + const path = request.url.path || ''; + const isAppRequest = path.startsWith('/app/'); + + if (!isAppRequest) { + return toolkit.next(); + } + + const authenticatedUser = security.authc.getCurrentUser(request); + const roles = authenticatedUser?.roles || []; + + if (!authenticatedUser || roles.length === 0) { + return toolkit.next(); + } + + const uiSettings = await getUiSettingsClient(); + const dashboardOnlyModeRoles = await uiSettings.get( + UI_SETTINGS.CONFIG_DASHBOARD_ONLY_MODE_ROLES + ); + + if (!dashboardOnlyModeRoles) { + return toolkit.next(); + } + + const isDashboardOnlyModeUser = roles.find((role) => dashboardOnlyModeRoles.includes(role)); + const isSuperUser = roles.find((role) => role === superuserRole); + + const enforceDashboardOnlyMode = isDashboardOnlyModeUser && !isSuperUser; + + if (enforceDashboardOnlyMode) { + if ( + path.startsWith('/app/home') || + path.startsWith('/app/kibana') || + path.startsWith('/app/dashboards') + ) { + const dashBoardModeUrl = `${http.basePath.get(request)}/app/dashboard_mode`; + // If the user is in "Dashboard only mode" they should only be allowed to see + // the dashboard app and none others. + + return response.redirected({ + headers: { + location: dashBoardModeUrl, + }, + }); + } + + if (path.startsWith('/app/dashboard_mode')) { + // let through requests to the dashboard_mode app + return toolkit.next(); + } + + return response.notFound(); + } + + return toolkit.next(); + }) as OnPostAuthHandler; diff --git a/x-pack/legacy/plugins/dashboard_mode/common/constants.js b/x-pack/plugins/dashboard_mode/server/interceptors/index.ts similarity index 72% rename from x-pack/legacy/plugins/dashboard_mode/common/constants.js rename to x-pack/plugins/dashboard_mode/server/interceptors/index.ts index c9a2378ac5d82..e0bf175d15029 100644 --- a/x-pack/legacy/plugins/dashboard_mode/common/constants.js +++ b/x-pack/plugins/dashboard_mode/server/interceptors/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CONFIG_DASHBOARD_ONLY_MODE_ROLES = 'xpackDashboardMode:roles'; +export { setupDashboardModeRequestInterceptor } from './dashboard_mode_request_interceptor'; diff --git a/x-pack/plugins/dashboard_mode/server/plugin.ts b/x-pack/plugins/dashboard_mode/server/plugin.ts new file mode 100644 index 0000000000000..8b56f71b667cb --- /dev/null +++ b/x-pack/plugins/dashboard_mode/server/plugin.ts @@ -0,0 +1,62 @@ +/* + * 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 { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + SavedObjectsClient, + Logger, +} from '../../../../src/core/server'; + +import { SecurityPluginSetup } from '../../security/server'; +import { setupDashboardModeRequestInterceptor } from './interceptors'; + +import { getUiSettings } from './ui_settings'; + +interface DashboardModeServerSetupDependencies { + security?: SecurityPluginSetup; +} + +export class DashboardModeServerPlugin implements Plugin { + private initializerContext: PluginInitializerContext; + private logger?: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; + } + + public setup(core: CoreSetup, { security }: DashboardModeServerSetupDependencies) { + this.logger = this.initializerContext.logger.get(); + + core.uiSettings.register(getUiSettings()); + + const getUiSettingsClient = async () => { + const [coreStart] = await core.getStartServices(); + const { savedObjects, uiSettings } = coreStart; + const savedObjectsClient = new SavedObjectsClient(savedObjects.createInternalRepository()); + + return uiSettings.asScopedToClient(savedObjectsClient); + }; + + if (security) { + const dashboardModeRequestInterceptor = setupDashboardModeRequestInterceptor({ + http: core.http, + security, + getUiSettingsClient, + }); + + core.http.registerOnPostAuth(dashboardModeRequestInterceptor); + + this.logger.debug(`registered DashboardModeRequestInterceptor`); + } + } + + public start(core: CoreStart) {} + + public stop() {} +} diff --git a/x-pack/plugins/dashboard_mode/server/ui_settings.ts b/x-pack/plugins/dashboard_mode/server/ui_settings.ts new file mode 100644 index 0000000000000..f692ec8a33fc9 --- /dev/null +++ b/x-pack/plugins/dashboard_mode/server/ui_settings.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; +import { UiSettingsParams } from 'kibana/server'; +import { UI_SETTINGS } from '../common'; + +const DASHBOARD_ONLY_USER_ROLE = 'kibana_dashboard_only_user'; + +export function getUiSettings(): Record> { + return { + [UI_SETTINGS.CONFIG_DASHBOARD_ONLY_MODE_ROLES]: { + name: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle', { + defaultMessage: 'Dashboards only roles', + }), + description: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription', { + defaultMessage: 'Roles that belong to View Dashboards Only mode', + }), + value: [DASHBOARD_ONLY_USER_ROLE], + category: ['dashboard'], + deprecation: { + message: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation', { + defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.', + }), + docLinksKey: 'dashboardSettings', + }, + schema: schema.arrayOf(schema.string()), + }, + }; +} diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 13301df471c53..fb1c218e1545b 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -217,7 +217,7 @@ export class IngestManagerPlugin encryptedSavedObjects: EncryptedSavedObjectsPluginStart; } ) { - appContextService.start({ + await appContextService.start({ encryptedSavedObjectsStart: plugins.encryptedSavedObjects, encryptedSavedObjectsSetup: this.encryptedSavedObjectsSetup, security: this.security, diff --git a/x-pack/test/api_integration/apis/fleet/index.js b/x-pack/test/api_integration/apis/fleet/index.js index a8c026ac6a1bd..df81b826132a9 100644 --- a/x-pack/test/api_integration/apis/fleet/index.js +++ b/x-pack/test/api_integration/apis/fleet/index.js @@ -6,6 +6,7 @@ export default function loadTests({ loadTestFile }) { describe('Fleet Endpoints', () => { + loadTestFile(require.resolve('./setup')); loadTestFile(require.resolve('./delete_agent')); loadTestFile(require.resolve('./list_agent')); loadTestFile(require.resolve('./unenroll_agent')); @@ -16,7 +17,6 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./enrollment_api_keys/crud')); loadTestFile(require.resolve('./install')); loadTestFile(require.resolve('./agents/actions')); - loadTestFile(require.resolve('./setup')); loadTestFile(require.resolve('./agent_flow')); }); } diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index b79dc3f3ffe59..3f3294c85d6df 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -27,9 +27,9 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./short_urls')); loadTestFile(require.resolve('./lens')); loadTestFile(require.resolve('./fleet')); - loadTestFile(require.resolve('./ingest_manager')); - loadTestFile(require.resolve('./endpoint')); loadTestFile(require.resolve('./ml')); loadTestFile(require.resolve('./transform')); + loadTestFile(require.resolve('./endpoint')); + loadTestFile(require.resolve('./ingest_manager')); }); } From 572d006d9fdc0c544e7188125f10e5d6113b8455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 23 Jun 2020 14:08:17 +0100 Subject: [PATCH 03/36] [Observability]Adding specific Return APIs for each plugin (#69257) * Adding specific apis for each plugin * adding metric hosts stat * addressing PR comment * addressing PR comments * changing series to key/value * exporting interfaces * adding label to stat * refactoring types Co-authored-by: Elastic Machine --- .../observability/public/data_handler.ts | 23 ++++- .../public/typings/data_handler/index.d.ts | 42 --------- .../typings/fetch_data_response/index.d.ts | 86 +++++++++++++++++++ 3 files changed, 107 insertions(+), 44 deletions(-) delete mode 100644 x-pack/plugins/observability/public/typings/data_handler/index.d.ts create mode 100644 x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts diff --git a/x-pack/plugins/observability/public/data_handler.ts b/x-pack/plugins/observability/public/data_handler.ts index 30a7357404d23..8f80f79b2e829 100644 --- a/x-pack/plugins/observability/public/data_handler.ts +++ b/x-pack/plugins/observability/public/data_handler.ts @@ -4,9 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FetchData, HasData } from './typings/data_handler'; +import { ObservabilityFetchDataResponse, FetchDataResponse } from './typings/fetch_data_response'; import { ObservabilityApp } from '../typings/common'; +interface FetchDataParams { + // The start timestamp in milliseconds of the queried time interval + startTime: string; + // The end timestamp in milliseconds of the queried time interval + endTime: string; + // The aggregation bucket size in milliseconds if applicable to the data source + bucketSize: string; +} + +export type FetchData = ( + fetchDataParams: FetchDataParams +) => Promise; +export type HasData = () => Promise; + interface DataHandler { fetchData: FetchData; hasData: HasData; @@ -14,7 +28,12 @@ interface DataHandler { const dataHandlers: Partial> = {}; -export type RegisterDataHandler = (params: { appName: ObservabilityApp } & DataHandler) => void; +export type RegisterDataHandler = (params: { + appName: T; + fetchData: FetchData; + hasData: HasData; +}) => void; + export const registerDataHandler: RegisterDataHandler = ({ appName, fetchData, hasData }) => { dataHandlers[appName] = { fetchData, hasData }; }; diff --git a/x-pack/plugins/observability/public/typings/data_handler/index.d.ts b/x-pack/plugins/observability/public/typings/data_handler/index.d.ts deleted file mode 100644 index a208e4e7c223d..0000000000000 --- a/x-pack/plugins/observability/public/typings/data_handler/index.d.ts +++ /dev/null @@ -1,42 +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. - */ - -interface Stat { - label: string; - value: string; - color?: string; -} - -export interface Coordinates { - x: number; - y?: number; -} - -interface Series { - label: string; - coordinates: Coordinates[]; - color?: string; - key?: string; -} - -interface FetchDataResponse { - title: string; - appLink: string; - stats: Stat[]; - series: Series[]; -} -interface FetchDataParams { - // The start timestamp in milliseconds of the queried time interval - startTime: string; - // The end timestamp in milliseconds of the queried time interval - endTime: string; - // The aggregation bucket size in milliseconds if applicable to the data source - bucketSize: string; -} - -export type FetchData = (fetchDataParams: FetchDataParams) => Promise; - -export type HasData = () => Promise; diff --git a/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts b/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts new file mode 100644 index 0000000000000..30ecb24a58a5a --- /dev/null +++ b/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts @@ -0,0 +1,86 @@ +/* + * 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. + */ + +interface Percentage { + label: string; + pct: number; + color?: string; +} +interface Bytes { + label: string; + bytes: number; + color?: string; +} +interface Numeral { + label: string; + value: number; + color?: string; +} + +export interface Coordinates { + x: number; + y?: number; +} + +interface Series { + label: string; + coordinates: Coordinates[]; + color?: string; +} + +export interface FetchDataResponse { + title: string; + appLink: string; +} + +export interface LogsFetchDataResponse extends FetchDataResponse { + stats: Record; + series: Record; +} + +export interface MetricsFetchDataResponse extends FetchDataResponse { + stats: { + hosts: Numeral; + cpu: Percentage; + memory: Percentage; + disk: Percentage; + inboundTraffic: Bytes; + outboundTraffic: Bytes; + }; + series: { + inboundTraffic: Series; + outboundTraffic: Series; + }; +} + +export interface UptimeFetchDataResponse extends FetchDataResponse { + stats: { + monitors: Numeral; + up: Numeral; + down: Numeral; + }; + series: { + up: Series; + down: Series; + }; +} + +export interface ApmFetchDataResponse extends FetchDataResponse { + stats: { + services: Numeral; + transactions: Numeral; + }; + series: { + transactions: Series; + }; +} + +export interface ObservabilityFetchDataResponse { + apm: ApmFetchDataResponse; + infra_metrics: MetricsFetchDataResponse; + infra_logs: LogsFetchDataResponse; + uptime: UptimeFetchDataResponse; +} From 43ed4e20432bd620ff94048c5ebe294afe75c19d Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 23 Jun 2020 15:55:58 +0200 Subject: [PATCH 04/36] [ML] fix ml api services (#69681) --- .../application/services/ml_api_service/index.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index 6d32fca6a645c..af6944d7ae2d2 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -109,7 +109,6 @@ export type MlApiServices = ReturnType; export const ml = mlApiServicesProvider(new HttpService(proxyHttpStart)); export function mlApiServicesProvider(httpService: HttpService) { - const { http } = httpService; return { getJobs(obj?: { jobId?: string }) { const jobId = obj && obj.jobId ? `/${obj.jobId}` : ''; @@ -142,14 +141,14 @@ export function mlApiServicesProvider(httpService: HttpService) { }, closeJob({ jobId }: { jobId: string }) { - return http({ + return httpService.http({ path: `${basePath()}/anomaly_detectors/${jobId}/_close`, method: 'POST', }); }, forceCloseJob({ jobId }: { jobId: string }) { - return http({ + return httpService.http({ path: `${basePath()}/anomaly_detectors/${jobId}/_close?force=true`, method: 'POST', }); @@ -278,14 +277,14 @@ export function mlApiServicesProvider(httpService: HttpService) { }, stopDatafeed({ datafeedId }: { datafeedId: string }) { - return http({ + return httpService.http({ path: `${basePath()}/datafeeds/${datafeedId}/_stop`, method: 'POST', }); }, forceStopDatafeed({ datafeedId }: { datafeedId: string }) { - return http({ + return httpService.http({ path: `${basePath()}/datafeeds/${datafeedId}/_stop?force=true`, method: 'POST', }); @@ -697,7 +696,7 @@ export function mlApiServicesProvider(httpService: HttpService) { }, getModelSnapshots(jobId: string, snapshotId?: string) { - return http({ + return httpService.http({ path: `${basePath()}/anomaly_detectors/${jobId}/model_snapshots${ snapshotId !== undefined ? `/${snapshotId}` : '' }`, @@ -709,7 +708,7 @@ export function mlApiServicesProvider(httpService: HttpService) { snapshotId: string, body: { description?: string; retain?: boolean } ) { - return http({ + return httpService.http({ path: `${basePath()}/anomaly_detectors/${jobId}/model_snapshots/${snapshotId}/_update`, method: 'POST', body: JSON.stringify(body), @@ -717,7 +716,7 @@ export function mlApiServicesProvider(httpService: HttpService) { }, deleteModelSnapshot(jobId: string, snapshotId: string) { - return http({ + return httpService.http({ path: `${basePath()}/anomaly_detectors/${jobId}/model_snapshots/${snapshotId}`, method: 'DELETE', }); From cc893f3da4ad92c6ec10cfc96d5a2ad362789775 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 23 Jun 2020 16:02:22 +0200 Subject: [PATCH 05/36] [Discover] Unskip context navigation functional test (#68771) --- .../apps/context/_context_navigation.js | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/test/functional/apps/context/_context_navigation.js b/test/functional/apps/context/_context_navigation.js index 92b109c7263ff..babefe488d7bc 100644 --- a/test/functional/apps/context/_context_navigation.js +++ b/test/functional/apps/context/_context_navigation.js @@ -17,12 +17,8 @@ * under the License. */ -import expect from '@kbn/expect'; - -const TEST_COLUMN_NAMES = ['@message']; const TEST_FILTER_COLUMN_NAMES = [ ['extension', 'jpg'], - ['geo.src', 'IN'], [ 'agent', 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', @@ -30,20 +26,15 @@ const TEST_FILTER_COLUMN_NAMES = [ ]; export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); const browser = getService('browser'); const docTable = getService('docTable'); const PageObjects = getPageObjects(['common', 'context', 'discover', 'timePicker']); - // FLAKY: https://github.com/elastic/kibana/issues/62866 - describe.skip('context link in discover', function contextSize() { + describe('discover - context - back navigation', function contextSize() { before(async function () { + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - await Promise.all( - TEST_COLUMN_NAMES.map((columnName) => - PageObjects.discover.clickFieldListItemAdd(columnName) - ) - ); for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) { await PageObjects.discover.clickFieldListItem(columnName); await PageObjects.discover.clickFieldListPlusFilter(columnName, value); @@ -51,18 +42,22 @@ export default function ({ getService, getPageObjects }) { }); it('should go back after loading', async function () { - // navigate to the context view - await docTable.clickRowToggle({ rowIndex: 0 }); - await (await docTable.getRowActions({ rowIndex: 0 }))[0].click(); - await PageObjects.context.waitUntilContextLoadingHasFinished(); - await PageObjects.context.clickSuccessorLoadMoreButton(); - await PageObjects.context.clickSuccessorLoadMoreButton(); - await PageObjects.context.clickSuccessorLoadMoreButton(); - await PageObjects.context.waitUntilContextLoadingHasFinished(); - await browser.goBack(); - await PageObjects.discover.waitForDocTableLoadingComplete(); - const hitCount = await PageObjects.discover.getHitCount(); - expect(hitCount).to.be('522'); + await retry.waitFor('user navigating to context and returning to discover', async () => { + // navigate to the context view + const initialHitCount = await PageObjects.discover.getHitCount(); + await docTable.clickRowToggle({ rowIndex: 0 }); + const rowActions = await docTable.getRowActions({ rowIndex: 0 }); + await rowActions[0].click(); + await PageObjects.context.waitUntilContextLoadingHasFinished(); + await PageObjects.context.clickSuccessorLoadMoreButton(); + await PageObjects.context.clickSuccessorLoadMoreButton(); + await PageObjects.context.clickSuccessorLoadMoreButton(); + await PageObjects.context.waitUntilContextLoadingHasFinished(); + await browser.goBack(); + await PageObjects.discover.waitForDocTableLoadingComplete(); + const hitCount = await PageObjects.discover.getHitCount(); + return initialHitCount === hitCount; + }); }); }); } From 8956a33e4c6abcbff31b6d178aaeadce38b6b513 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Tue, 23 Jun 2020 16:34:50 +0200 Subject: [PATCH 06/36] [APM] Use asPercent to format breakdown chart (#69384) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Søren Louv-Jansen Co-authored-by: Elastic Machine --- .../TransactionBreakdownGraph/index.tsx | 3 +-- .../TransactionBreakdownKpiList.tsx | 6 ++---- .../utils/formatters/__test__/formatters.test.ts | 10 +++++++--- .../plugins/apm/public/utils/formatters/formatters.ts | 8 ++++++++ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index 0afed6c3c1fa8..2cb3696f88002 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -5,7 +5,6 @@ */ import React, { useMemo } from 'react'; -import numeral from '@elastic/numeral'; import { throttle } from 'lodash'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; @@ -21,7 +20,7 @@ interface Props { } const tickFormatY = (y: Maybe) => { - return numeral(y || 0).format('0 %'); + return asPercent(y ?? 0, 1); }; const formatTooltipValue = (coordinate: Coordinate) => { diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownKpiList.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownKpiList.tsx index eda8f19c949a3..3898679f83537 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownKpiList.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownKpiList.tsx @@ -13,7 +13,7 @@ import { EuiIcon, } from '@elastic/eui'; import styled from 'styled-components'; -import { FORMATTERS, InfraFormatterType } from '../../../../../infra/public'; +import { asPercent } from '../../../utils/formatters'; interface TransactionBreakdownKpi { name: string; @@ -65,9 +65,7 @@ const TransactionBreakdownKpiList: React.FC = ({ kpis }) => { - - {FORMATTERS[InfraFormatterType.percent](kpi.percentage)} - + {asPercent(kpi.percentage, 1)} diff --git a/x-pack/plugins/apm/public/utils/formatters/__test__/formatters.test.ts b/x-pack/plugins/apm/public/utils/formatters/__test__/formatters.test.ts index f6ed88a850a5b..66101baf3a746 100644 --- a/x-pack/plugins/apm/public/utils/formatters/__test__/formatters.test.ts +++ b/x-pack/plugins/apm/public/utils/formatters/__test__/formatters.test.ts @@ -7,12 +7,16 @@ import { asPercent } from '../formatters'; describe('formatters', () => { describe('asPercent', () => { - it('should divide and format item as percent', () => { - expect(asPercent(3725, 10000, 'n/a')).toEqual('37.3%'); + it('should format as integer when number is above 10', () => { + expect(asPercent(3725, 10000, 'n/a')).toEqual('37%'); + }); + + it('should add a decimal when value is below 10', () => { + expect(asPercent(0.092, 1)).toEqual('9.2%'); }); it('should format when numerator is 0', () => { - expect(asPercent(0, 1, 'n/a')).toEqual('0.0%'); + expect(asPercent(0, 1, 'n/a')).toEqual('0%'); }); it('should return fallback when denominator is undefined', () => { diff --git a/x-pack/plugins/apm/public/utils/formatters/formatters.ts b/x-pack/plugins/apm/public/utils/formatters/formatters.ts index 9fdac85c7154f..649f11063b149 100644 --- a/x-pack/plugins/apm/public/utils/formatters/formatters.ts +++ b/x-pack/plugins/apm/public/utils/formatters/formatters.ts @@ -34,5 +34,13 @@ export function asPercent( } const decimal = numerator / denominator; + + // 33.2 => 33% + // 3.32 => 3.3% + // 0 => 0% + if (Math.abs(decimal) >= 0.1 || decimal === 0) { + return numeral(decimal).format('0%'); + } + return numeral(decimal).format('0.0%'); } From 0e2f9fc3651d3eb81a2ea76aea6fee8d02d7d447 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Tue, 23 Jun 2020 08:59:32 -0700 Subject: [PATCH 07/36] Updates swim lane UI text (#69454) --- .../explorer/add_to_dashboard_control.tsx | 6 ++--- .../services/dashboard_service.test.ts | 8 +++---- .../application/services/explorer_service.ts | 24 +++++++++---------- .../anomaly_swimlane_embeddable.tsx | 2 +- .../anomaly_swimlane_embeddable_factory.ts | 2 +- .../anomaly_swimlane_initializer.tsx | 6 ++--- .../explorer_swimlane_container.tsx | 2 +- .../ui_actions/edit_swimlane_panel_action.tsx | 2 +- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx index cb11a33ccfd76..16e2fb47a209d 100644 --- a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx +++ b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx @@ -57,7 +57,7 @@ interface AddToDashboardControlProps { } /** - * Component for attaching anomaly swimlane embeddable to dashboards. + * Component for attaching anomaly swim lane embeddable to dashboards. */ export const AddToDashboardControl: FC = ({ onClose, @@ -225,7 +225,7 @@ export const AddToDashboardControl: FC = ({ @@ -234,7 +234,7 @@ export const AddToDashboardControl: FC = ({ label={ } > diff --git a/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts b/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts index 6cab23eb187c7..a618534d7ae00 100644 --- a/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts +++ b/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts @@ -70,12 +70,12 @@ describe('DashboardService', () => { gridData: { x: 24, y: 0, w: 24, h: 15, i: '0aa334bd-8308-4ded-9462-80dbd37680ee' }, panelIndex: '0aa334bd-8308-4ded-9462-80dbd37680ee', embeddableConfig: { - title: 'ML anomaly swimlane for fb_population_1', + title: 'ML anomaly swim lane for fb_population_1', jobIds: ['fb_population_1'], limit: 5, swimlaneType: 'overall', }, - title: 'ML anomaly swimlane for fb_population_1', + title: 'ML anomaly swim lane for fb_population_1', }, { version: '8.0.0', @@ -118,12 +118,12 @@ describe('DashboardService', () => { gridData: { x: 24, y: 0, w: 24, h: 15, i: '0aa334bd-8308-4ded-9462-80dbd37680ee' }, panelIndex: '0aa334bd-8308-4ded-9462-80dbd37680ee', embeddableConfig: { - title: 'ML anomaly swimlane for fb_population_1', + title: 'ML anomaly swim lane for fb_population_1', jobIds: ['fb_population_1'], limit: 5, swimlaneType: 'overall', }, - title: 'ML anomaly swimlane for fb_population_1', + title: 'ML anomaly swim lane for fb_population_1', }, { version: '8.0.0', diff --git a/x-pack/plugins/ml/public/application/services/explorer_service.ts b/x-pack/plugins/ml/public/application/services/explorer_service.ts index 717ed3ba64c37..0944328db0052 100644 --- a/x-pack/plugins/ml/public/application/services/explorer_service.ts +++ b/x-pack/plugins/ml/public/application/services/explorer_service.ts @@ -55,8 +55,8 @@ export class ExplorerService { const intervalSeconds = this.timeBuckets.getInterval().asSeconds(); - // if the swimlane cell widths are too small they will not be visible - // calculate how many buckets will be drawn before the swimlanes are actually rendered + // if the swim lane cell widths are too small they will not be visible + // calculate how many buckets will be drawn before the swim lanes are actually rendered // and increase the interval to widen the cells if they're going to be smaller than 8px // this has to be done at this stage so all searches use the same interval const timerangeSeconds = (bounds.max!.valueOf() - bounds.min!.valueOf()) / 1000; @@ -81,7 +81,7 @@ export class ExplorerService { } /** - * Loads overall swimlane data + * Loads overall swim lane data * @param selectedJobs * @param chartWidth */ @@ -97,13 +97,13 @@ export class ExplorerService { const bounds = this.getTimeBounds(); - // Ensure the search bounds align to the bucketing interval used in the swimlane so + // Ensure the search bounds align to the bucketing interval used in the swim lane so // that the first and last buckets are complete. const searchBounds = getBoundsRoundedToInterval(bounds, interval, false); const selectedJobIds = selectedJobs.map((d) => d.id); // Load the overall bucket scores by time. - // Pass the interval in seconds as the swimlane relies on a fixed number of seconds between buckets + // Pass the interval in seconds as the swim lane relies on a fixed number of seconds between buckets // which wouldn't be the case if e.g. '1M' was used. // Pass 'true' when obtaining bucket bounds due to the way the overall_buckets endpoint works // to ensure the search is inclusive of end time. @@ -125,7 +125,7 @@ export class ExplorerService { ); // eslint-disable-next-line no-console - console.log('Explorer overall swimlane data set:', overallSwimlaneData); + console.log('Explorer overall swim lane data set:', overallSwimlaneData); return overallSwimlaneData; } @@ -158,7 +158,7 @@ export class ExplorerService { const selectedJobIds = selectedJobs.map((d) => d.id); // load scores by influencer/jobId value and time. - // Pass the interval in seconds as the swimlane relies on a fixed number of seconds between buckets + // Pass the interval in seconds as the swim lane relies on a fixed number of seconds between buckets // which wouldn't be the case if e.g. '1M' was used. const interval = `${swimlaneBucketInterval.asSeconds()}s`; @@ -199,7 +199,7 @@ export class ExplorerService { swimlaneBucketInterval.asSeconds() ); // eslint-disable-next-line no-console - console.log('Explorer view by swimlane data set:', viewBySwimlaneData); + console.log('Explorer view by swim lane data set:', viewBySwimlaneData); return viewBySwimlaneData; } @@ -227,7 +227,7 @@ export class ExplorerService { }; // Store the earliest and latest times of the data returned by the ES aggregations, - // These will be used for calculating the earliest and latest times for the swimlane charts. + // These will be used for calculating the earliest and latest times for the swim lane charts. Object.entries(scoresByTime).forEach(([timeMs, score]) => { const time = Number(timeMs) / 1000; dataset.points.push({ @@ -250,7 +250,7 @@ export class ExplorerService { viewBySwimlaneFieldName: string, interval: number ): OverallSwimlaneData { - // Processes the scores for the 'view by' swimlane. + // Processes the scores for the 'view by' swim lane. // Sorts the lanes according to the supplied array of lane // values in the order in which they should be displayed, // or pass an empty array to sort lanes according to max score over all time. @@ -259,7 +259,7 @@ export class ExplorerService { points: [], laneLabels: [], interval, - // Set the earliest and latest to be the same as the overall swimlane. + // Set the earliest and latest to be the same as the overall swim lane. earliest: bounds.earliest, latest: bounds.latest, }; @@ -295,7 +295,7 @@ export class ExplorerService { }); } else { // Sort lanes according to supplied order - // e.g. when a cell in the overall swimlane has been selected. + // e.g. when a cell in the overall swim lane has been selected. // Find the index of each lane label from the actual data set, // rather than using sortedLaneValues as-is, just in case they differ. dataset.laneLabels = dataset.laneLabels.sort((a, b) => { diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx index b4b25db452bdb..3b4562628051e 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx @@ -32,7 +32,7 @@ export const ANOMALY_SWIMLANE_EMBEDDABLE_TYPE = 'ml_anomaly_swimlane'; export const getDefaultPanelTitle = (jobIds: JobId[]) => i18n.translate('xpack.ml.swimlaneEmbeddable.title', { - defaultMessage: 'ML anomaly swimlane for {jobIds}', + defaultMessage: 'ML anomaly swim lane for {jobIds}', values: { jobIds: jobIds.join(', ') }, }); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts index 09091b21e49b6..37c2cfb3e029b 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts @@ -39,7 +39,7 @@ export class AnomalySwimlaneEmbeddableFactory public getDisplayName() { return i18n.translate('xpack.ml.components.jobAnomalyScoreEmbeddable.displayName', { - defaultMessage: 'ML Anomaly Swimlane', + defaultMessage: 'ML Anomaly Swim Lane', }); } diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx index 4c93b9ef23239..4977ece54bb57 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx @@ -92,7 +92,7 @@ export const AnomalySwimlaneInitializer: FC = ( @@ -121,7 +121,7 @@ export const AnomalySwimlaneInitializer: FC = ( label={ } > @@ -131,7 +131,7 @@ export const AnomalySwimlaneInitializer: FC = ( color="primary" isFullWidth legend={i18n.translate('xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel', { - defaultMessage: 'Swimlane type', + defaultMessage: 'Swim lane type', })} options={swimlaneTypeOptions} idSelected={swimlaneType} diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx index 0bba9b59f7bf7..db2b9d55cfabb 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx @@ -69,7 +69,7 @@ export const ExplorerSwimlaneContainer: FC = ({ title={ } color="danger" diff --git a/x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx b/x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx index 915fe3cc8f948..312b9f31124b1 100644 --- a/x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx +++ b/x-pack/plugins/ml/public/ui_actions/edit_swimlane_panel_action.tsx @@ -32,7 +32,7 @@ export function createEditSwimlanePanelAction(getStartServices: CoreSetup['getSt }, getDisplayName: () => i18n.translate('xpack.ml.actions.editSwimlaneTitle', { - defaultMessage: 'Edit swimlane', + defaultMessage: 'Edit swim lane', }), execute: async ({ embeddable }: EditSwimlanePanelContext) => { if (!embeddable) { From 65c3114abc0c12bafbccb90959c9d80d8f5832e5 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Tue, 23 Jun 2020 18:09:07 +0200 Subject: [PATCH 08/36] Fix Advanced Settings Panel number editing in Graph (#69672) --- .../settings/advanced_settings_form.tsx | 43 +++++++++++++------ .../components/settings/settings.test.tsx | 22 ++++++++++ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx b/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx index 191655ec7bc17..51f802edec9d0 100644 --- a/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx +++ b/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { EuiFormRow, EuiFieldNumber, EuiComboBox, EuiSwitch, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SettingsProps } from './settings'; @@ -26,13 +26,30 @@ export function AdvancedSettingsForm({ updateSettings, allFields, }: Pick) { + // keep a local state during changes + const [formState, updateFormState] = useState({ ...advancedSettings }); + // useEffect update localState only based on the main store + useEffect(() => { + updateFormState(advancedSettings); + }, [updateFormState, advancedSettings]); + function updateSetting(key: K, value: AdvancedSettings[K]) { updateSettings({ ...advancedSettings, [key]: value }); } function getNumberUpdater>(key: K) { - return function ({ target: { valueAsNumber } }: { target: { valueAsNumber: number } }) { - updateSetting(key, Number.isNaN(valueAsNumber) ? 1 : valueAsNumber); + return function ({ + target: { valueAsNumber, value }, + }: { + target: { valueAsNumber: number; value: string }; + }) { + // if the value is valid, then update the central store, otherwise only the local store + if (Number.isNaN(valueAsNumber)) { + // localstate update + return updateFormState({ ...formState, [key]: value }); + } + // do not worry about local store here, the useEffect will pick that up and sync it + updateSetting(key, valueAsNumber); }; } @@ -52,7 +69,7 @@ export function AdvancedSettingsForm({ fullWidth min={1} step={1} - value={advancedSettings.sampleSize} + value={formState.sampleSize} onChange={getNumberUpdater('sampleSize')} />
@@ -73,7 +90,7 @@ export function AdvancedSettingsForm({ { defaultMessage: 'Significant links' } )} id="graphSignificance" - checked={advancedSettings.useSignificance} + checked={formState.useSignificance} onChange={({ target: { checked } }) => updateSetting('useSignificance', checked)} />
@@ -91,7 +108,7 @@ export function AdvancedSettingsForm({ fullWidth min={1} step={1} - value={advancedSettings.minDocCount} + value={formState.minDocCount} onChange={getNumberUpdater('minDocCount')} /> @@ -127,11 +144,11 @@ export function AdvancedSettingsForm({ singleSelection={{ asPlainText: true }} options={allFields.map((field) => ({ label: field.name, value: field }))} selectedOptions={ - advancedSettings.sampleDiversityField + formState.sampleDiversityField ? [ { - label: advancedSettings.sampleDiversityField.name, - value: advancedSettings.sampleDiversityField, + label: formState.sampleDiversityField.name, + value: formState.sampleDiversityField, }, ] : [] @@ -145,7 +162,7 @@ export function AdvancedSettingsForm({ /> - {advancedSettings.sampleDiversityField && ( + {formState.sampleDiversityField && ( {advancedSettings.sampleDiversityField.name}{' '} + {formState.sampleDiversityField.name}{' '} {i18n.translate( 'xpack.graph.settings.advancedSettings.maxValuesInputHelpText.fieldText', { @@ -171,7 +188,7 @@ export function AdvancedSettingsForm({ fullWidth min={1} step={1} - value={advancedSettings.maxValuesPerDoc} + value={formState.maxValuesPerDoc} onChange={getNumberUpdater('maxValuesPerDoc')} /> @@ -190,7 +207,7 @@ export function AdvancedSettingsForm({ fullWidth min={1} step={1} - value={advancedSettings.timeoutMillis} + value={formState.timeoutMillis} onChange={getNumberUpdater('timeoutMillis')} append={ diff --git a/x-pack/plugins/graph/public/components/settings/settings.test.tsx b/x-pack/plugins/graph/public/components/settings/settings.test.tsx index b392a26ecf0d3..1efaead002b52 100644 --- a/x-pack/plugins/graph/public/components/settings/settings.test.tsx +++ b/x-pack/plugins/graph/public/components/settings/settings.test.tsx @@ -177,6 +177,28 @@ describe('settings', () => { ) ); }); + + it('should let the user edit and empty the field to input a new number', () => { + act(() => { + input('Sample size').prop('onChange')!({ + target: { value: '', valueAsNumber: NaN }, + } as React.ChangeEvent); + }); + // Central state should not be called + expect(dispatchSpy).not.toHaveBeenCalledWith( + updateSettings( + expect.objectContaining({ + timeoutMillis: 10000, + sampleSize: NaN, + }) + ) + ); + + // Update the local state + instance.update(); + // Now check that local state should reflect what the user sent + expect(input('Sample size').prop('value')).toEqual(''); + }); }); describe('blacklist', () => { From 98a897736bbd1dd0af78585db7350dd44509acf1 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Tue, 23 Jun 2020 12:21:48 -0400 Subject: [PATCH 09/36] Remove App communication from URL (#67064) Removed all inter-app communication via url in favour of a new service in the embeddable start contract called the state transfer service. --- .../application/dashboard_app_controller.tsx | 19 +- .../embeddable/dashboard_container.tsx | 7 +- .../dashboard_container_factory.tsx | 10 +- .../embeddable/grid/dashboard_grid.test.tsx | 1 + .../viewport/dashboard_viewport.test.tsx | 1 + .../viewport/dashboard_viewport.tsx | 2 +- src/plugins/dashboard/public/plugin.tsx | 5 +- src/plugins/embeddable/public/index.ts | 5 +- .../lib/actions/edit_panel_action.test.tsx | 24 ++- .../public/lib/actions/edit_panel_action.ts | 36 ++-- src/plugins/embeddable/public/lib/index.ts | 1 + .../public/lib/panel/embeddable_panel.tsx | 8 +- .../embeddable_state_transfer.test.ts | 167 ++++++++++++++++++ .../embeddable_state_transfer.ts | 132 ++++++++++++++ .../public/lib/state_transfer/index.ts | 21 +++ .../public/lib/state_transfer/types.ts | 56 ++++++ src/plugins/embeddable/public/mocks.tsx | 12 ++ src/plugins/embeddable/public/plugin.tsx | 67 ++++--- .../embeddable/public/tests/container.test.ts | 1 + src/plugins/embeddable/public/types.ts | 2 - .../visualize_embeddable_factory.tsx | 16 +- .../public/wizard/new_vis_modal.test.tsx | 15 +- .../public/wizard/new_vis_modal.tsx | 29 +-- .../public/wizard/show_new_vis.tsx | 5 + src/plugins/visualize/kibana.json | 3 +- .../public/application/editor/editor.js | 29 ++- .../visualize/public/kibana_services.ts | 4 +- src/plugins/visualize/public/plugin.ts | 6 +- .../lens/public/app_plugin/app.test.tsx | 29 +-- x-pack/plugins/lens/public/app_plugin/app.tsx | 23 +-- .../lens/public/app_plugin/mounter.tsx | 29 ++- x-pack/plugins/lens/public/plugin.ts | 5 +- 32 files changed, 600 insertions(+), 170 deletions(-) create mode 100644 src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts create mode 100644 src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts create mode 100644 src/plugins/embeddable/public/lib/state_transfer/index.ts create mode 100644 src/plugins/embeddable/public/lib/state_transfer/types.ts diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 206ef4f3d4313..3c559a6cde211 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -75,7 +75,7 @@ import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; import { RenderDeps } from './application'; -import { IKbnUrlStateStorage, removeQueryParam, unhashUrl } from '../../../kibana_utils/public'; +import { IKbnUrlStateStorage, unhashUrl } from '../../../kibana_utils/public'; import { addFatalError, AngularHttpError, @@ -132,6 +132,7 @@ export class DashboardAppController { embeddable, share, dashboardCapabilities, + scopedHistory, embeddableCapabilities: { visualizeCapabilities, mapsCapabilities }, data: { query: queryService }, core: { @@ -425,15 +426,13 @@ export class DashboardAppController { refreshDashboardContainer(); }); - // This code needs to be replaced with a better mechanism for adding new embeddables of - // any type from the add panel. Likely this will happen via creating a visualization "inline", - // without navigating away from the UX. - if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) { - const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]; - const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID]; - container.addNewEmbeddable(type, { savedObjectId: id }); - removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE); - removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID); + const incomingState = embeddable + .getStateTransfer(scopedHistory()) + .getIncomingEmbeddablePackage(); + if (incomingState) { + container.addNewEmbeddable(incomingState.type, { + savedObjectId: incomingState.id, + }); } } diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 27bbc2ef675a1..f1ecd0f221926 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -46,6 +46,7 @@ import { } from '../../../../kibana_react/public'; import { PLACEHOLDER_EMBEDDABLE } from './placeholder'; import { PanelPlacementMethod, IPanelPlacementArgs } from './panel/dashboard_panel_placement'; +import { EmbeddableStateTransfer } from '../../../../embeddable/public'; export interface DashboardContainerInput extends ContainerInput { viewMode: ViewMode; @@ -98,9 +99,12 @@ export class DashboardContainer extends Container React.ReactNode); + private embeddablePanel: EmbeddableStart['EmbeddablePanel']; + constructor( initialInput: DashboardContainerInput, private readonly options: DashboardContainerOptions, + stateTransfer?: EmbeddableStateTransfer, parent?: Container ) { super( @@ -111,6 +115,7 @@ export class DashboardContainer extends Container , diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx index 514b4e7ca8588..4107a00ba80ce 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; -import { CoreStart } from 'src/core/public'; +import { CoreStart, ScopedHistory } from 'src/core/public'; import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { EmbeddableFactory, EmbeddableStart } from '../../../../embeddable/public'; import { @@ -54,7 +54,10 @@ export class DashboardContainerFactoryDefinition public readonly isContainerType = true; public readonly type = DASHBOARD_CONTAINER_TYPE; - constructor(private readonly getStartServices: () => Promise) {} + constructor( + private readonly getStartServices: () => Promise, + private getHistory: () => ScopedHistory + ) {} public isEditable = async () => { const { capabilities } = await this.getStartServices(); @@ -81,6 +84,7 @@ export class DashboardContainerFactoryDefinition parent?: Container ): Promise => { const services = await this.getStartServices(); - return new DashboardContainer(initialInput, services, parent); + const stateTransfer = services.embeddable.getStateTransfer(this.getHistory()); + return new DashboardContainer(initialInput, services, stateTransfer, parent); }; } diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index 9a2610a82b97d..493ae8eb51797 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -64,6 +64,7 @@ function prepare(props?: Partial) { embeddable: { getTriggerCompatibleActions: (() => []) as any, getEmbeddableFactories: start.getEmbeddableFactories, + getEmbeddablePanel: jest.fn(), getEmbeddableFactory, } as any, notifications: {} as any, diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index 25e451dc7f793..733ea11c1eb64 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -54,6 +54,7 @@ function getProps( application: applicationServiceMock.createStartContract(), embeddable: { getTriggerCompatibleActions: (() => []) as any, + getEmbeddablePanel: jest.fn(), getEmbeddableFactories: start.getEmbeddableFactories, getEmbeddableFactory: start.getEmbeddableFactory, } as any, diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index 9ee50426b19bb..15a486f99a37f 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -26,8 +26,8 @@ import { context } from '../../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; - renderEmpty?: () => React.ReactNode; PanelComponent: EmbeddableStart['EmbeddablePanel']; + renderEmpty?: () => React.ReactNode; } interface State { diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index e99b8ad616cb9..5e2cb60965396 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -206,7 +206,10 @@ export class DashboardPlugin }; }; - const factory = new DashboardContainerFactoryDefinition(getStartServices); + const factory = new DashboardContainerFactoryDefinition( + getStartServices, + () => this.currentHistory! + ); embeddable.registerEmbeddableFactory(factory.type, factory); const placeholderFactory = new PlaceholderEmbeddableFactory(); diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index d595197373a21..1d1dc79121937 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -22,7 +22,6 @@ import './index.scss'; import { PluginInitializerContext } from 'src/core/public'; import { EmbeddablePublicPlugin } from './plugin'; -export { EMBEDDABLE_ORIGINATING_APP_PARAM } from './types'; export { ACTION_ADD_PANEL, ACTION_APPLY_FILTER, @@ -69,6 +68,9 @@ export { isSavedObjectEmbeddableInput, isRangeSelectTriggerContext, isValueClickTriggerContext, + EmbeddableStateTransfer, + EmbeddableOriginatingAppState, + EmbeddablePackageState, EmbeddableRenderer, EmbeddableRendererProps, } from './lib'; @@ -82,4 +84,5 @@ export { EmbeddableStart, EmbeddableSetupDependencies, EmbeddableStartDependencies, + EmbeddablePanelHOC, } from './plugin'; diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx index 196bd593eb8d5..4b602efb02717 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx @@ -23,11 +23,13 @@ import { ViewMode } from '../types'; import { ContactCardEmbeddable } from '../test_samples'; import { embeddablePluginMock } from '../../mocks'; import { applicationServiceMock } from '../../../../../core/public/mocks'; +import { of } from 'rxjs'; const { doStart } = embeddablePluginMock.createInstance(); const start = doStart(); const getFactory = start.getEmbeddableFactory; const applicationMock = applicationServiceMock.createStartContract(); +const stateTransferMock = embeddablePluginMock.createStartContract().getStateTransfer(); class EditableEmbeddable extends Embeddable { public readonly type = 'EDITABLE_EMBEDDABLE'; @@ -43,7 +45,7 @@ class EditableEmbeddable extends Embeddable { } test('is compatible when edit url is available, in edit mode and editable', async () => { - const action = new EditPanelAction(getFactory, applicationMock); + const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); expect( await action.isCompatible({ embeddable: new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true), @@ -51,8 +53,20 @@ test('is compatible when edit url is available, in edit mode and editable', asyn ).toBe(true); }); +test('redirects to app using state transfer', async () => { + applicationMock.currentAppId$ = of('superCoolCurrentApp'); + const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); + const embeddable = new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true); + embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' })); + await action.execute({ embeddable }); + expect(stateTransferMock.navigateToWithOriginatingApp).toHaveBeenCalledWith('ultraVisualize', { + path: '/123', + state: { originatingApp: 'superCoolCurrentApp' }, + }); +}); + test('getHref returns the edit urls', async () => { - const action = new EditPanelAction(getFactory, applicationMock); + const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); expect(action.getHref).toBeDefined(); if (action.getHref) { @@ -66,7 +80,7 @@ test('getHref returns the edit urls', async () => { }); test('is not compatible when edit url is not available', async () => { - const action = new EditPanelAction(getFactory, applicationMock); + const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); const embeddable = new ContactCardEmbeddable( { id: '123', @@ -85,7 +99,7 @@ test('is not compatible when edit url is not available', async () => { }); test('is not visible when edit url is available but in view mode', async () => { - const action = new EditPanelAction(getFactory, applicationMock); + const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); expect( await action.isCompatible({ embeddable: new EditableEmbeddable( @@ -100,7 +114,7 @@ test('is not visible when edit url is available but in view mode', async () => { }); test('is not compatible when edit url is available, in edit mode, but not editable', async () => { - const action = new EditPanelAction(getFactory, applicationMock); + const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); expect( await action.isCompatible({ embeddable: new EditableEmbeddable( diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index 0bb2206988d83..d983dc9f41853 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -24,7 +24,7 @@ import { take } from 'rxjs/operators'; import { ViewMode } from '../types'; import { EmbeddableFactoryNotFoundError } from '../errors'; import { EmbeddableStart } from '../../plugin'; -import { EMBEDDABLE_ORIGINATING_APP_PARAM, IEmbeddable } from '../..'; +import { IEmbeddable, EmbeddableOriginatingAppState, EmbeddableStateTransfer } from '../..'; export const ACTION_EDIT_PANEL = 'editPanel'; @@ -32,6 +32,12 @@ interface ActionContext { embeddable: IEmbeddable; } +interface NavigationContext { + app: string; + path: string; + state?: EmbeddableOriginatingAppState; +} + export class EditPanelAction implements Action { public readonly type = ACTION_EDIT_PANEL; public readonly id = ACTION_EDIT_PANEL; @@ -40,7 +46,8 @@ export class EditPanelAction implements Action { constructor( private readonly getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'], - private readonly application: ApplicationStart + private readonly application: ApplicationStart, + private readonly stateTransfer?: EmbeddableStateTransfer ) { if (this.application?.currentAppId$) { this.application.currentAppId$ @@ -79,9 +86,15 @@ export class EditPanelAction implements Action { public async execute(context: ActionContext) { const appTarget = this.getAppTarget(context); - if (appTarget) { - await this.application.navigateToApp(appTarget.app, { path: appTarget.path }); + if (this.stateTransfer && appTarget.state) { + await this.stateTransfer.navigateToWithOriginatingApp(appTarget.app, { + path: appTarget.path, + state: appTarget.state, + }); + } else { + await this.application.navigateToApp(appTarget.app, { path: appTarget.path }); + } return; } @@ -92,22 +105,17 @@ export class EditPanelAction implements Action { } } - public getAppTarget({ embeddable }: ActionContext): { app: string; path: string } | undefined { + public getAppTarget({ embeddable }: ActionContext): NavigationContext | undefined { const app = embeddable ? embeddable.getOutput().editApp : undefined; - let path = embeddable ? embeddable.getOutput().editPath : undefined; + const path = embeddable ? embeddable.getOutput().editPath : undefined; if (app && path) { - if (this.currentAppId) { - path += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`; - } - return { app, path }; + const state = this.currentAppId ? { originatingApp: this.currentAppId } : undefined; + return { app, path, state }; } } public async getHref({ embeddable }: ActionContext): Promise { - let editUrl = embeddable ? embeddable.getOutput().editUrl : undefined; - if (editUrl && this.currentAppId) { - editUrl += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`; - } + const editUrl = embeddable ? embeddable.getOutput().editUrl : undefined; return editUrl ? editUrl : ''; } } diff --git a/src/plugins/embeddable/public/lib/index.ts b/src/plugins/embeddable/public/lib/index.ts index 172d9bdc9ec47..b757fa59a7f3a 100644 --- a/src/plugins/embeddable/public/lib/index.ts +++ b/src/plugins/embeddable/public/lib/index.ts @@ -24,3 +24,4 @@ export * from './actions'; export * from './triggers'; export * from './containers'; export * from './panel'; +export * from './state_transfer'; diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 9948d5a2db3db..8cf2e015f88cf 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -43,6 +43,7 @@ import { EditPanelAction } from '../actions'; import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal'; import { EmbeddableStart } from '../../plugin'; import { EmbeddableErrorLabel } from './embeddable_error_label'; +import { EmbeddableStateTransfer } from '..'; const sortByOrderField = ( { order: orderA }: { order?: number }, @@ -62,6 +63,7 @@ interface Props { application: CoreStart['application']; inspector: InspectorStartContract; SavedObjectFinder: React.ComponentType; + stateTransfer?: EmbeddableStateTransfer; hideHeader?: boolean; } @@ -299,7 +301,11 @@ export class EmbeddablePanel extends React.Component { ), new InspectPanelAction(this.props.inspector), new RemovePanelAction(), - new EditPanelAction(this.props.getEmbeddableFactory, this.props.application), + new EditPanelAction( + this.props.getEmbeddableFactory, + this.props.application, + this.props.stateTransfer + ), ]; const sortedActions = [...regularActions, ...extraActions].sort(sortByOrderField); diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts new file mode 100644 index 0000000000000..0d5ae6be68185 --- /dev/null +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts @@ -0,0 +1,167 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { coreMock, scopedHistoryMock } from '../../../../../core/public/mocks'; +import { EmbeddableStateTransfer } from '.'; +import { ApplicationStart, ScopedHistory } from '../../../../../core/public'; + +function mockHistoryState(state: unknown) { + return scopedHistoryMock.create({ state }); +} + +describe('embeddable state transfer', () => { + let application: jest.Mocked; + let stateTransfer: EmbeddableStateTransfer; + const destinationApp = 'superUltraVisualize'; + const originatingApp = 'superUltraTestDashboard'; + + beforeEach(() => { + const core = coreMock.createStart(); + application = core.application; + stateTransfer = new EmbeddableStateTransfer(application.navigateToApp); + }); + + it('can send an outgoing originating app state', async () => { + await stateTransfer.navigateToWithOriginatingApp(destinationApp, { state: { originatingApp } }); + expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { + state: { originatingApp: 'superUltraTestDashboard' }, + }); + }); + + it('can send an outgoing originating app state in append mode', async () => { + const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + (historyMock as unknown) as ScopedHistory + ); + await stateTransfer.navigateToWithOriginatingApp(destinationApp, { + state: { originatingApp }, + appendToExistingState: true, + }); + expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { + path: undefined, + state: { + kibanaIsNowForSports: 'extremeSportsKibana', + originatingApp: 'superUltraTestDashboard', + }, + }); + }); + + it('can send an outgoing embeddable package state', async () => { + await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { + state: { type: 'coolestType', id: '150' }, + }); + expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { + state: { type: 'coolestType', id: '150' }, + }); + }); + + it('can send an outgoing embeddable package state in append mode', async () => { + const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + (historyMock as unknown) as ScopedHistory + ); + await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { + state: { type: 'coolestType', id: '150' }, + appendToExistingState: true, + }); + expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { + path: undefined, + state: { kibanaIsNowForSports: 'extremeSportsKibana', type: 'coolestType', id: '150' }, + }); + }); + + it('can fetch an incoming originating app state', async () => { + const historyMock = mockHistoryState({ originatingApp: 'extremeSportsKibana' }); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + (historyMock as unknown) as ScopedHistory + ); + const fetchedState = stateTransfer.getIncomingOriginatingApp(); + expect(fetchedState).toEqual({ originatingApp: 'extremeSportsKibana' }); + }); + + it('returns undefined with originating app state is not in the right shape', async () => { + const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + (historyMock as unknown) as ScopedHistory + ); + const fetchedState = stateTransfer.getIncomingOriginatingApp(); + expect(fetchedState).toBeUndefined(); + }); + + it('can fetch an incoming embeddable package state', async () => { + const historyMock = mockHistoryState({ type: 'skisEmbeddable', id: '123' }); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + (historyMock as unknown) as ScopedHistory + ); + const fetchedState = stateTransfer.getIncomingEmbeddablePackage(); + expect(fetchedState).toEqual({ type: 'skisEmbeddable', id: '123' }); + }); + + it('returns undefined when embeddable package is not in the right shape', async () => { + const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + (historyMock as unknown) as ScopedHistory + ); + const fetchedState = stateTransfer.getIncomingEmbeddablePackage(); + expect(fetchedState).toBeUndefined(); + }); + + it('removes all keys in the keysToRemoveAfterFetch array', async () => { + const historyMock = mockHistoryState({ + type: 'skisEmbeddable', + id: '123', + test1: 'test1', + test2: 'test2', + }); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + (historyMock as unknown) as ScopedHistory + ); + stateTransfer.getIncomingEmbeddablePackage({ keysToRemoveAfterFetch: ['type', 'id'] }); + expect(historyMock.replace).toHaveBeenCalledWith( + expect.objectContaining({ state: { test1: 'test1', test2: 'test2' } }) + ); + }); + + it('leaves state as is when no keysToRemove are supplied', async () => { + const historyMock = mockHistoryState({ + type: 'skisEmbeddable', + id: '123', + test1: 'test1', + test2: 'test2', + }); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + (historyMock as unknown) as ScopedHistory + ); + stateTransfer.getIncomingEmbeddablePackage(); + expect(historyMock.location.state).toEqual({ + type: 'skisEmbeddable', + id: '123', + test1: 'test1', + test2: 'test2', + }); + }); +}); diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts new file mode 100644 index 0000000000000..57b425d2df45c --- /dev/null +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { cloneDeep } from 'lodash'; +import { ScopedHistory, ApplicationStart } from '../../../../../core/public'; +import { + EmbeddableOriginatingAppState, + isEmbeddableOriginatingAppState, + EmbeddablePackageState, + isEmbeddablePackageState, +} from './types'; + +/** + * A wrapper around the state object in {@link ScopedHistory | core scoped history} which provides + * strongly typed helper methods for common incoming and outgoing states used by the embeddable infrastructure. + * + * @public + */ +export class EmbeddableStateTransfer { + constructor( + private navigateToApp: ApplicationStart['navigateToApp'], + private scopedHistory?: ScopedHistory + ) {} + + /** + * Fetches an {@link EmbeddableOriginatingAppState | originating app} argument from the scoped + * history's location state. + * + * @param history - the scoped history to fetch from + * @param options.keysToRemoveAfterFetch - an array of keys to be removed from the state after they are retrieved + */ + public getIncomingOriginatingApp(options?: { + keysToRemoveAfterFetch?: string[]; + }): EmbeddableOriginatingAppState | undefined { + return this.getIncomingState(isEmbeddableOriginatingAppState, { + keysToRemoveAfterFetch: options?.keysToRemoveAfterFetch, + }); + } + + /** + * Fetches an {@link EmbeddablePackageState | embeddable package} argument from the scoped + * history's location state. + * + * @param history - the scoped history to fetch from + * @param options.keysToRemoveAfterFetch - an array of keys to be removed from the state after they are retrieved + */ + public getIncomingEmbeddablePackage(options?: { + keysToRemoveAfterFetch?: string[]; + }): EmbeddablePackageState | undefined { + return this.getIncomingState(isEmbeddablePackageState, { + keysToRemoveAfterFetch: options?.keysToRemoveAfterFetch, + }); + } + + /** + * A wrapper around the {@link ApplicationStart.navigateToApp} method which navigates to the specified appId + * with {@link EmbeddableOriginatingAppState | originating app state} + */ + public async navigateToWithOriginatingApp( + appId: string, + options?: { + path?: string; + state: EmbeddableOriginatingAppState; + appendToExistingState?: boolean; + } + ): Promise { + await this.navigateToWithState(appId, options); + } + + /** + * A wrapper around the {@link ApplicationStart.navigateToApp} method which navigates to the specified appId + * with {@link EmbeddablePackageState | embeddable package state} + */ + public async navigateToWithEmbeddablePackage( + appId: string, + options?: { path?: string; state: EmbeddablePackageState; appendToExistingState?: boolean } + ): Promise { + await this.navigateToWithState(appId, options); + } + + private getIncomingState( + guard: (state: unknown) => state is IncomingStateType, + options?: { + keysToRemoveAfterFetch?: string[]; + } + ): IncomingStateType | undefined { + if (!this.scopedHistory) { + throw new TypeError('ScopedHistory is required to fetch incoming state'); + } + const incomingState = this.scopedHistory.location?.state; + const castState = + !guard || guard(incomingState) ? (cloneDeep(incomingState) as IncomingStateType) : undefined; + if (castState && options?.keysToRemoveAfterFetch) { + const stateReplace = { ...(this.scopedHistory.location.state as { [key: string]: unknown }) }; + options.keysToRemoveAfterFetch.forEach((key: string) => { + delete stateReplace[key]; + }); + this.scopedHistory.replace({ ...this.scopedHistory.location, state: stateReplace }); + } + return castState; + } + + private async navigateToWithState( + appId: string, + options?: { path?: string; state?: OutgoingStateType; appendToExistingState?: boolean } + ): Promise { + const stateObject = + options?.appendToExistingState && this.scopedHistory + ? { + ...(this.scopedHistory?.location.state as { [key: string]: unknown }), + ...options.state, + } + : options?.state; + await this.navigateToApp(appId, { path: options?.path, state: stateObject }); + } +} diff --git a/src/plugins/embeddable/public/lib/state_transfer/index.ts b/src/plugins/embeddable/public/lib/state_transfer/index.ts new file mode 100644 index 0000000000000..e51efc5dcca26 --- /dev/null +++ b/src/plugins/embeddable/public/lib/state_transfer/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { EmbeddableStateTransfer } from './embeddable_state_transfer'; +export { EmbeddableOriginatingAppState, EmbeddablePackageState } from './types'; diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts new file mode 100644 index 0000000000000..8eae441d1be23 --- /dev/null +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Represents a state package that contains the last active app id. + * @public + */ +export interface EmbeddableOriginatingAppState { + originatingApp: string; +} + +export function isEmbeddableOriginatingAppState( + state: unknown +): state is EmbeddableOriginatingAppState { + return ensureFieldOfTypeExists('originatingApp', state, 'string'); +} + +/** + * Represents a state package that contains all fields necessary to create an embeddable in a container. + * @public + */ +export interface EmbeddablePackageState { + type: string; + id: string; +} + +export function isEmbeddablePackageState(state: unknown): state is EmbeddablePackageState { + return ( + ensureFieldOfTypeExists('type', state, 'string') && + ensureFieldOfTypeExists('id', state, 'string') + ); +} + +function ensureFieldOfTypeExists(key: string, obj: unknown, type?: string): boolean { + return ( + obj && + key in (obj as { [key: string]: unknown }) && + (!type || typeof (obj as { [key: string]: unknown })[key] === type) + ); +} diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index 9da0b7602c4f8..49910525c7ab1 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -22,6 +22,7 @@ import { EmbeddableSetup, EmbeddableSetupDependencies, EmbeddableStartDependencies, + EmbeddableStateTransfer, IEmbeddable, EmbeddablePanel, } from '.'; @@ -75,6 +76,15 @@ export const createEmbeddablePanelMock = ({ ); }; +export const createEmbeddableStateTransferMock = (): Partial => { + return { + getIncomingOriginatingApp: jest.fn(), + getIncomingEmbeddablePackage: jest.fn(), + navigateToWithOriginatingApp: jest.fn(), + navigateToWithEmbeddablePackage: jest.fn(), + }; +}; + const createSetupContract = (): Setup => { const setupContract: Setup = { registerEmbeddableFactory: jest.fn(), @@ -88,6 +98,8 @@ const createStartContract = (): Start => { getEmbeddableFactories: jest.fn(), getEmbeddableFactory: jest.fn(), EmbeddablePanel: jest.fn(), + getEmbeddablePanel: jest.fn(), + getStateTransfer: jest.fn(() => createEmbeddableStateTransferMock() as EmbeddableStateTransfer), }; return startContract; }; diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 269b1ffed829b..c4e0ca44a4e7e 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -20,7 +20,13 @@ import React from 'react'; import { getSavedObjectFinder } from '../../saved_objects/public'; import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; import { Start as InspectorStart } from '../../inspector/public'; -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + ScopedHistory, +} from '../../../core/public'; import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types'; import { bootstrap } from './bootstrap'; import { @@ -32,6 +38,7 @@ import { EmbeddablePanel, } from './lib'; import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition'; +import { EmbeddableStateTransfer } from './lib/state_transfer'; export interface EmbeddableSetupDependencies { uiActions: UiActionsSetup; @@ -63,9 +70,13 @@ export interface EmbeddableStart { embeddableFactoryId: string ) => EmbeddableFactory | undefined; getEmbeddableFactories: () => IterableIterator; - EmbeddablePanel: React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>; + EmbeddablePanel: EmbeddablePanelHOC; + getEmbeddablePanel: (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC; + getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer; } +export type EmbeddablePanelHOC = React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>; + export class EmbeddablePublicPlugin implements Plugin { private readonly embeddableFactoryDefinitions: Map< string, @@ -73,6 +84,7 @@ export class EmbeddablePublicPlugin implements Plugin = new Map(); private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map(); private customEmbeddableFactoryProvider?: EmbeddableFactoryProvider; + private outgoingOnlyStateTransfer: EmbeddableStateTransfer = {} as EmbeddableStateTransfer; private isRegistryReady = false; constructor(initializerContext: PluginInitializerContext) {} @@ -105,31 +117,42 @@ export class EmbeddablePublicPlugin implements Plugin ({ + embeddable, + hideHeader, + }: { + embeddable: IEmbeddable; + hideHeader?: boolean; + }) => ( + + ); + return { getEmbeddableFactory: this.getEmbeddableFactory, getEmbeddableFactories: this.getEmbeddableFactories, - EmbeddablePanel: ({ - embeddable, - hideHeader, - }: { - embeddable: IEmbeddable; - hideHeader?: boolean; - }) => ( - - ), + getStateTransfer: (history?: ScopedHistory) => { + return history + ? new EmbeddableStateTransfer(core.application.navigateToApp, history) + : this.outgoingOnlyStateTransfer; + }, + EmbeddablePanel: getEmbeddablePanelHoc(), + getEmbeddablePanel: getEmbeddablePanelHoc, }; } diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts index 6a388fe61782a..e6162748fdb68 100644 --- a/src/plugins/embeddable/public/tests/container.test.ts +++ b/src/plugins/embeddable/public/tests/container.test.ts @@ -82,6 +82,7 @@ async function creatHelloWorldContainerAndEmbeddable( getEmbeddableFactory: start.getEmbeddableFactory, panelComponent: testPanel, }); + const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, diff --git a/src/plugins/embeddable/public/types.ts b/src/plugins/embeddable/public/types.ts index a57af862f2a34..2d112b2359818 100644 --- a/src/plugins/embeddable/public/types.ts +++ b/src/plugins/embeddable/public/types.ts @@ -26,8 +26,6 @@ import { EmbeddableFactoryDefinition, } from './lib/embeddables'; -export const EMBEDDABLE_ORIGINATING_APP_PARAM = 'embeddableOriginatingApp'; - export type EmbeddableFactoryRegistry = Map; export type EmbeddableFactoryProvider = < diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index c4267c9a36f78..eb4b66401820f 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -26,7 +26,6 @@ import { EmbeddableOutput, ErrorEmbeddable, IContainer, - EMBEDDABLE_ORIGINATING_APP_PARAM, } from '../../../embeddable/public'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; @@ -50,7 +49,7 @@ interface VisualizationAttributes extends SavedObjectAttributes { } export interface VisualizeEmbeddableFactoryDeps { - start: StartServicesGetter>; + start: StartServicesGetter>; } export class VisualizeEmbeddableFactory @@ -103,15 +102,7 @@ export class VisualizeEmbeddableFactory } public async getCurrentAppId() { - let currentAppId = await this.deps - .start() - .core.application.currentAppId$.pipe(first()) - .toPromise(); - // TODO: Remove this after https://github.com/elastic/kibana/pull/63443 - if (currentAppId === 'kibana') { - currentAppId += `:${window.location.hash.split(/[\/\?]/)[1]}`; - } - return currentAppId; + return await this.deps.start().core.application.currentAppId$.pipe(first()).toPromise(); } public async createFromSavedObject( @@ -136,9 +127,8 @@ export class VisualizeEmbeddableFactory public async create() { // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. - const originatingAppParam = await this.getCurrentAppId(); showNewVisModal({ - editorParams: [`${EMBEDDABLE_ORIGINATING_APP_PARAM}=${originatingAppParam}`], + originatingApp: await this.getCurrentAppId(), outsideVisualizeApp: true, }); return undefined; diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx index 57e30631becbc..dd89e98fb8fe5 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx @@ -22,6 +22,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TypesStart, VisType } from '../vis_types'; import { NewVisModal } from './new_vis_modal'; import { ApplicationStart, SavedObjectsStart } from '../../../../core/public'; +import { embeddablePluginMock } from '../../../embeddable/public/mocks'; describe('NewVisModal', () => { const defaultVisTypeParams = { @@ -144,30 +145,34 @@ describe('NewVisModal', () => { ); }); - it('closes and redirects properly if visualization with aliasPath and addToDashboard in editorParams', () => { + it('closes and redirects properly if visualization with aliasPath and originatingApp in props', () => { const onClose = jest.fn(); const navigateToApp = jest.fn(); + const stateTransfer = embeddablePluginMock.createStartContract().getStateTransfer(); const wrapper = mountWithIntl( ); const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); visButton.simulate('click'); - expect(navigateToApp).toBeCalledWith('otherApp', { - path: '#/aliasUrl?embeddableOriginatingApp=notAnApp', + expect(stateTransfer.navigateToWithOriginatingApp).toBeCalledWith('otherApp', { + path: '#/aliasUrl', + state: { originatingApp: 'coolJestTestApp' }, }); expect(onClose).toHaveBeenCalled(); }); - it('closes and redirects properly if visualization with aliasApp and without addToDashboard in editorParams', () => { + it('closes and redirects properly if visualization with aliasApp and without originatingApp in props', () => { const onClose = jest.fn(); const navigateToApp = jest.fn(); const wrapper = mountWithIntl( diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 1a970e505b7c7..84a5bca0ed0ed 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -28,7 +28,7 @@ import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisType, VisTypeAlias } from '../vis_types'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; -import { EMBEDDABLE_ORIGINATING_APP_PARAM } from '../../../embeddable/public'; +import { EmbeddableStateTransfer } from '../../../embeddable/public'; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants'; interface TypeSelectionProps { @@ -42,6 +42,8 @@ interface TypeSelectionProps { usageCollection?: UsageCollectionSetup; application: ApplicationStart; outsideVisualizeApp?: boolean; + stateTransfer?: EmbeddableStateTransfer; + originatingApp?: string; } interface TypeSelectionState { @@ -148,14 +150,8 @@ class NewVisModal extends React.Component - param.startsWith(EMBEDDABLE_ORIGINATING_APP_PARAM) - ); - params = originatingAppParam ? `${params}?${originatingAppParam}` : params; - } this.props.onClose(); - this.props.application.navigateToApp(visType.aliasApp, { path: params }); + this.navigate(visType.aliasApp, visType.aliasPath); return; } @@ -168,13 +164,24 @@ class NewVisModal extends React.Component void; + originatingApp?: string; outsideVisualizeApp?: boolean; } @@ -45,6 +47,7 @@ export interface ShowNewVisModalParams { export function showNewVisModal({ editorParams = [], onClose, + originatingApp, outsideVisualizeApp, }: ShowNewVisModalParams = {}) { const container = document.createElement('div'); @@ -65,6 +68,8 @@ export function showNewVisModal({ originatingApp; const visStateToEditorState = () => { @@ -647,7 +646,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState */ function doSave(saveOptions) { // vis.title was not bound and it's needed to reflect title into visState - const firstSave = !Boolean(savedVis.id); + const newlyCreated = !Boolean(savedVis.id) || savedVis.copyOnSave; stateContainer.transitions.setVis({ title: savedVis.title, type: savedVis.type || stateContainer.getState().vis.type, @@ -680,16 +679,14 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState // Manually insert a new url so the back button will open the saved visualization. history.replace(appPath); setActiveUrl(appPath); - const lastAppType = $scope.getOriginatingApp(); - - if (lastAppType === 'dashboards') { - const savedVisId = firstSave || savedVis.copyOnSave ? savedVis.id : ''; - dashboard.addEmbeddableToDashboard({ - embeddableId: savedVisId, - embeddableType: VISUALIZE_EMBEDDABLE_TYPE, - }); + if (newlyCreated && embeddable) { + embeddable + .getStateTransfer() + .navigateToWithEmbeddablePackage($scope.getOriginatingApp(), { + state: { id: savedVis.id, type: VISUALIZE_EMBEDDABLE_TYPE }, + }); } else { - application.navigateToApp(lastAppType); + application.navigateToApp($scope.getOriginatingApp()); } } else if (savedVis.id === $route.current.params.id) { chrome.docTitle.change(savedVis.lastSavedTitle); diff --git a/src/plugins/visualize/public/kibana_services.ts b/src/plugins/visualize/public/kibana_services.ts index 8d714f1844345..d954a3f4925ac 100644 --- a/src/plugins/visualize/public/kibana_services.ts +++ b/src/plugins/visualize/public/kibana_services.ts @@ -34,8 +34,8 @@ import { DataPublicPluginStart } from '../../data/public'; import { VisualizationsStart } from '../../visualizations/public'; import { SavedVisualizations } from './application/types'; import { KibanaLegacyStart } from '../../kibana_legacy/public'; -import { DashboardStart } from '../../dashboard/public'; import { SavedObjectsStart } from '../../saved_objects/public'; +import { EmbeddableStart } from '../../embeddable/public'; export interface VisualizeKibanaServices { pluginInitializerContext: PluginInitializerContext; @@ -52,7 +52,7 @@ export interface VisualizeKibanaServices { kibanaLegacy: KibanaLegacyStart; visualizeCapabilities: any; visualizations: VisualizationsStart; - dashboard: DashboardStart; + embeddable: EmbeddableStart; I18nContext: I18nStart['Context']; setActiveUrl: (newUrl: string) => void; createVisEmbeddableFromObject: VisualizationsStart['__LEGACY']['createVisEmbeddableFromObject']; diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index 8a05adc18964a..3bd111084e34b 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -40,16 +40,16 @@ import { VisualizationsStart } from '../../visualizations/public'; import { VisualizeConstants } from './application/visualize_constants'; import { setServices, VisualizeKibanaServices } from './kibana_services'; import { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../home/public'; -import { DashboardStart } from '../../dashboard/public'; import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { SavedObjectsStart } from '../../saved_objects/public'; +import { EmbeddableStart } from '../../embeddable/public'; export interface VisualizePluginStartDependencies { data: DataPublicPluginStart; navigation: NavigationStart; share?: SharePluginStart; visualizations: VisualizationsStart; - dashboard: DashboardStart; + embeddable: EmbeddableStart; kibanaLegacy: KibanaLegacyStart; savedObjects: SavedObjectsStart; } @@ -129,11 +129,11 @@ export class VisualizePlugin toastNotifications: coreStart.notifications.toasts, visualizeCapabilities: coreStart.application.capabilities.visualize, visualizations: pluginsStart.visualizations, + embeddable: pluginsStart.embeddable, I18nContext: coreStart.i18n.Context, setActiveUrl, createVisEmbeddableFromObject: pluginsStart.visualizations.__LEGACY.createVisEmbeddableFromObject, - dashboard: pluginsStart.dashboard, scopedHistory: () => this.currentHistory!, savedObjects: pluginsStart.savedObjects, }; diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 53498a8e5afa1..cd6fbf96d6750 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -124,12 +124,7 @@ describe('Lens App', () => { storage: Storage; docId?: string; docStorage: SavedObjectStore; - redirectTo: ( - id?: string, - returnToOrigin?: boolean, - originatingApp?: string | undefined, - newlyCreated?: boolean - ) => void; + redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; originatingApp: string | undefined; onAppLeave: AppMountParameters['onAppLeave']; history: History; @@ -168,14 +163,7 @@ describe('Lens App', () => { load: jest.fn(), save: jest.fn(), }, - redirectTo: jest.fn( - ( - id?: string, - returnToOrigin?: boolean, - originatingApp?: string | undefined, - newlyCreated?: boolean - ) => {} - ), + redirectTo: jest.fn((id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => {}), onAppLeave: jest.fn(), history: createMemoryHistory(), } as unknown) as jest.Mocked<{ @@ -186,12 +174,7 @@ describe('Lens App', () => { storage: Storage; docId?: string; docStorage: SavedObjectStore; - redirectTo: ( - id?: string, - returnToOrigin?: boolean, - originatingApp?: string | undefined, - newlyCreated?: boolean - ) => void; + redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; originatingApp: string | undefined; onAppLeave: AppMountParameters['onAppLeave']; history: History; @@ -533,7 +516,7 @@ describe('Lens App', () => { expression: 'kibana 3', }); - expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, undefined, true); + expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true); inst.setProps({ docId: 'aaa' }); @@ -553,7 +536,7 @@ describe('Lens App', () => { expression: 'kibana 3', }); - expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, undefined, true); + expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true); inst.setProps({ docId: 'aaa' }); @@ -621,7 +604,7 @@ describe('Lens App', () => { title: 'hello there', }); - expect(args.redirectTo).toHaveBeenCalledWith('aaa', true, undefined, true); + expect(args.redirectTo).toHaveBeenCalledWith('aaa', true, true); }); it('saves app filters and does not save pinned filters', async () => { diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index fc8d5dd9eb395..0ab547bed6d37 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -43,7 +43,6 @@ interface State { isLoading: boolean; isSaveModalVisible: boolean; indexPatternsForTopNav: IndexPatternInstance[]; - originatingApp: string | undefined; persistedDoc?: Document; lastKnownDoc?: Document; @@ -65,7 +64,7 @@ export function App({ docId, docStorage, redirectTo, - originatingAppFromUrl, + originatingApp, navigation, onAppLeave, history, @@ -77,13 +76,8 @@ export function App({ storage: IStorageWrapper; docId?: string; docStorage: SavedObjectStore; - redirectTo: ( - id?: string, - returnToOrigin?: boolean, - originatingApp?: string | undefined, - newlyCreated?: boolean - ) => void; - originatingAppFromUrl?: string | undefined; + redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; + originatingApp?: string | undefined; onAppLeave: AppMountParameters['onAppLeave']; history: History; }) { @@ -98,7 +92,6 @@ export function App({ isSaveModalVisible: false, indexPatternsForTopNav: [], query: { query: '', language }, - originatingApp: originatingAppFromUrl, dateRange: { fromDate: currentRange.from, toDate: currentRange.to, @@ -316,7 +309,7 @@ export function App({ lastKnownDoc: newDoc, })); if (docId !== id || saveProps.returnToOrigin) { - redirectTo(id, saveProps.returnToOrigin, state.originatingApp, newlyCreated); + redirectTo(id, saveProps.returnToOrigin, newlyCreated); } }) .catch((e) => { @@ -356,7 +349,7 @@ export function App({
{ if (isSaveable && lastKnownDoc) { setState((s) => ({ ...s, isSaveModalVisible: true })); @@ -509,7 +502,7 @@ export function App({
{lastKnownDoc && state.isSaveModalVisible && ( runSave(props)} onClose={() => setState((s) => ({ ...s, isSaveModalVisible: false }))} documentInfo={{ diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index e6a9119ad4306..7a33241792a58 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -10,9 +10,8 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; -import { parse } from 'query-string'; -import { removeQueryParam, Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry'; @@ -29,7 +28,7 @@ export async function mountApp( createEditorFrame: EditorFrameStart['createInstance'] ) { const [coreStart, startDependencies] = await core.getStartServices(); - const { data: dataStart, navigation } = startDependencies; + const { data: dataStart, navigation, embeddable } = startDependencies; const savedObjectsClient = coreStart.savedObjects.client; addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks); @@ -37,6 +36,10 @@ export async function mountApp( i18n.translate('xpack.lens.pageTitle', { defaultMessage: 'Lens' }) ); + const stateTransfer = embeddable?.getStateTransfer(params.history); + const { originatingApp } = + stateTransfer?.getIncomingOriginatingApp({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; + const instance = await createEditorFrame(); setReportManager( @@ -49,7 +52,6 @@ export async function mountApp( routeProps: RouteComponentProps<{ id?: string }>, id?: string, returnToOrigin?: boolean, - originatingApp?: string, newlyCreated?: boolean ) => { if (!id) { @@ -59,11 +61,9 @@ export async function mountApp( } else if (!!originatingApp && id && returnToOrigin) { routeProps.history.push(`/edit/${id}`); - if (originatingApp === 'dashboards') { - const addLensId = newlyCreated ? id : ''; - startDependencies.dashboard.addEmbeddableToDashboard({ - embeddableId: addLensId, - embeddableType: LENS_EMBEDDABLE_TYPE, + if (newlyCreated && stateTransfer) { + stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { + state: { id, type: LENS_EMBEDDABLE_TYPE }, }); } else { coreStart.application.navigateToApp(originatingApp); @@ -73,11 +73,6 @@ export async function mountApp( const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => { trackUiEvent('loaded'); - const urlParams = parse(routeProps.location.search) as Record; - const originatingAppFromUrl = urlParams.embeddableOriginatingApp; - if (urlParams.embeddableOriginatingApp) { - removeQueryParam(routeProps.history, 'embeddableOriginatingApp'); - } return ( - redirectTo(routeProps, id, returnToOrigin, originatingApp, newlyCreated) + redirectTo={(id, returnToOrigin, newlyCreated) => + redirectTo(routeProps, id, returnToOrigin, newlyCreated) } - originatingAppFromUrl={originatingAppFromUrl} + originatingApp={originatingApp} onAppLeave={params.onAppLeave} history={routeProps.history} /> diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 3000c9321b3b9..25458d86f0e12 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -6,7 +6,7 @@ import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; -import { EmbeddableSetup } from 'src/plugins/embeddable/public'; +import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public'; import { VisualizationsSetup } from 'src/plugins/visualizations/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; @@ -26,7 +26,6 @@ import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import './index.scss'; -import { DashboardStart } from '../../../../src/plugins/dashboard/public'; export interface LensPluginSetupDependencies { kibanaLegacy: KibanaLegacySetup; @@ -41,7 +40,7 @@ export interface LensPluginStartDependencies { expressions: ExpressionsStart; navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; - dashboard: DashboardStart; + embeddable: EmbeddableStart; } export class LensPlugin { From 5fb206f67ffcd953934d330ff1ebaea9bfd02c15 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Tue, 23 Jun 2020 12:35:02 -0400 Subject: [PATCH 10/36] [Security_Solution] Split up indices (#69589) * Fixing resolver alert generation * Splitting indices up * Removing tests that could randomly fail because of the generation code * Adding support for multiple indices * Updating archives with the new index names * Removing alerts data stream * Switching to process instead of fake Co-authored-by: Elastic Machine --- .../common/endpoint/constants.ts | 3 +- .../common/endpoint/index_data.ts | 17 +++++--- .../endpoint_alerts/store/middleware.ts | 6 +-- .../scripts/endpoint/resolver_generator.ts | 18 +++++++-- .../endpoint/alerts/handlers/details/index.ts | 4 +- .../endpoint/alerts/handlers/list/index.ts | 4 +- .../server/endpoint/routes/resolver/alerts.ts | 4 +- .../endpoint/routes/resolver/ancestry.ts | 4 +- .../endpoint/routes/resolver/children.ts | 4 +- .../server/endpoint/routes/resolver/events.ts | 4 +- .../routes/resolver/queries/alerts.ts | 2 +- .../endpoint/routes/resolver/queries/base.ts | 7 +++- .../routes/resolver/queries/children.ts | 2 +- .../routes/resolver/queries/events.ts | 2 +- .../server/endpoint/routes/resolver/tree.ts | 4 +- .../endpoint/routes/resolver/utils/fetch.ts | 25 +++++++----- .../apis/endpoint/alerts/index.ts | 9 +++-- .../apis/endpoint/data_stream_helper.ts | 5 +++ .../api_integration/apis/endpoint/resolver.ts | 25 ++++++++---- .../test/api_integration/services/resolver.ts | 37 ++++++++++++------ .../endpoint/alerts/api_feature/data.json.gz | Bin 16803 -> 16685 bytes .../alerts/host_api_feature/data.json.gz | Bin 888 -> 886 bytes .../endpoint/metadata/api_feature/data.json | 18 ++++----- .../es_archives/endpoint/policy/data.json.gz | Bin 1327 -> 1323 bytes 24 files changed, 130 insertions(+), 74 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index bb00321567e80..e311e358e6146 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export const eventsIndexPattern = 'events-endpoint-*'; +export const eventsIndexPattern = 'logs-endpoint.events.*'; +export const alertsIndexPattern = 'logs-endpoint.alerts-*'; export const metadataIndexPattern = 'metrics-endpoint.metadata-*'; export const policyIndexPattern = 'metrics-endpoint.policy-*'; export const telemetryIndexPattern = 'metrics-endpoint.telemetry-*'; diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts index d868ba63b1edc..00b8f0b057afd 100644 --- a/x-pack/plugins/security_solution/common/endpoint/index_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts @@ -16,6 +16,7 @@ export async function indexHostsAndAlerts( metadataIndex: string, policyIndex: string, eventIndex: string, + alertIndex: string, alertsPerHost: number, options: TreeOptions = {} ) { @@ -23,7 +24,7 @@ export async function indexHostsAndAlerts( for (let i = 0; i < numHosts; i++) { const generator = new EndpointDocGenerator(random); await indexHostDocs(numDocs, client, metadataIndex, policyIndex, generator); - await indexAlerts(client, eventIndex, generator, alertsPerHost, options); + await indexAlerts(client, eventIndex, alertIndex, generator, alertsPerHost, options); } await client.indices.refresh({ index: eventIndex, @@ -65,7 +66,8 @@ async function indexHostDocs( async function indexAlerts( client: Client, - index: string, + eventIndex: string, + alertIndex: string, generator: EndpointDocGenerator, numAlerts: number, options: TreeOptions = {} @@ -82,9 +84,14 @@ async function indexAlerts( } const body = resolverDocs.reduce( // eslint-disable-next-line @typescript-eslint/no-explicit-any - (array: Array>, doc) => ( - array.push({ create: { _index: index } }, doc), array - ), + (array: Array>, doc) => { + let index = eventIndex; + if (doc.event.kind === 'alert') { + index = alertIndex; + } + array.push({ create: { _index: index } }, doc); + return array; + }, [] ); await client.bulk({ body, refresh: 'true' }); diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/store/middleware.ts b/x-pack/plugins/security_solution/public/endpoint_alerts/store/middleware.ts index ae5d36ce0d1ff..8fabce4c4fec2 100644 --- a/x-pack/plugins/security_solution/public/endpoint_alerts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/endpoint_alerts/store/middleware.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { eventsIndexPattern } from '../../../common/endpoint/constants'; +import { alertsIndexPattern } from '../../../common/endpoint/constants'; import { IIndexPattern } from '../../../../../../src/plugins/data/public'; import { AlertResultList, @@ -49,10 +49,10 @@ export const alertMiddlewareFactory: ImmutableMiddlewareFactory async function fetchIndexPatterns(): Promise { const { indexPatterns } = depsStart.data; const fields = await indexPatterns.getFieldsForWildcard({ - pattern: eventsIndexPattern, + pattern: alertsIndexPattern, }); const indexPattern: IIndexPattern = { - title: eventsIndexPattern, + title: alertsIndexPattern, fields, }; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts index 542991a927f7a..53fa59060550f 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts @@ -95,19 +95,25 @@ async function main() { eventIndex: { alias: 'ei', describe: 'index to store events in', - default: 'events-endpoint-1', + default: 'logs-endpoint.events.process-default', + type: 'string', + }, + alertIndex: { + alias: 'ai', + describe: 'index to store alerts in', + default: 'logs-endpoint.alerts-default', type: 'string', }, metadataIndex: { alias: 'mi', describe: 'index to store host metadata in', - default: 'metrics-endpoint.metadata-default-1', + default: 'metrics-endpoint.metadata-default', type: 'string', }, policyIndex: { alias: 'pi', describe: 'index to store host policy in', - default: 'metrics-endpoint.policy-default-1', + default: 'metrics-endpoint.policy-default', type: 'string', }, ancestors: { @@ -192,7 +198,10 @@ async function main() { const client = new Client(clientOptions); if (argv.delete) { - await deleteIndices([argv.eventIndex, argv.metadataIndex, argv.policyIndex], client); + await deleteIndices( + [argv.eventIndex, argv.metadataIndex, argv.policyIndex, argv.alertIndex], + client + ); } let seed = argv.seed; @@ -209,6 +218,7 @@ async function main() { argv.metadataIndex, argv.policyIndex, argv.eventIndex, + argv.alertIndex, argv.alertsPerHost, { ancestors: argv.ancestors, diff --git a/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/details/index.ts b/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/details/index.ts index 5bb3f969807df..6433705290936 100644 --- a/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/details/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/details/index.ts @@ -5,7 +5,7 @@ */ import { GetResponse } from 'elasticsearch'; import { KibanaRequest, RequestHandler } from 'kibana/server'; -import { eventsIndexPattern } from '../../../../../common/endpoint/constants'; +import { alertsIndexPattern } from '../../../../../common/endpoint/constants'; import { AlertEvent } from '../../../../../common/endpoint/types'; import { EndpointAppContext } from '../../../types'; import { AlertDetailsRequestParams } from '../../../../../common/endpoint_alerts/types'; @@ -34,7 +34,7 @@ export const alertDetailsHandlerWrapper = function ( ctx, req.params, response, - eventsIndexPattern + alertsIndexPattern ); const currentHostInfo = await getHostData( diff --git a/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/list/index.ts b/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/list/index.ts index 5122dd89bba16..24b940bf80ba5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/list/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/list/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { RequestHandler } from 'kibana/server'; -import { eventsIndexPattern } from '../../../../../common/endpoint/constants'; +import { alertsIndexPattern } from '../../../../../common/endpoint/constants'; import { EndpointAppContext } from '../../../types'; import { searchESForAlerts } from '../lib'; import { getRequestData, mapToAlertResultList } from './lib'; @@ -23,7 +23,7 @@ export const alertListHandlerWrapper = function ( const response = await searchESForAlerts( ctx.core.elasticsearch.legacy.client, reqData, - eventsIndexPattern + alertsIndexPattern ); const mappedBody = await mapToAlertResultList(ctx, endpointAppContext, reqData, response); return res.ok({ body: mappedBody }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/alerts.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/alerts.ts index 7bfe101b920fb..830d92ef2efc0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/alerts.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/alerts.ts @@ -7,7 +7,7 @@ import { TypeOf } from '@kbn/config-schema'; import { RequestHandler, Logger } from 'kibana/server'; import { validateAlerts } from '../../../../common/endpoint/schema/resolver'; -import { eventsIndexPattern } from '../../../../common/endpoint/constants'; +import { alertsIndexPattern, eventsIndexPattern } from '../../../../common/endpoint/constants'; import { Fetcher } from './utils/fetch'; import { EndpointAppContext } from '../../types'; @@ -23,7 +23,7 @@ export function handleAlerts( try { const client = context.core.elasticsearch.legacy.client; - const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID); + const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID); return res.ok({ body: await fetcher.alerts(alerts, afterAlert), diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/ancestry.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/ancestry.ts index aa040638045b2..cff80ec5010bd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/ancestry.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/ancestry.ts @@ -6,7 +6,7 @@ import { RequestHandler, Logger } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; -import { eventsIndexPattern } from '../../../../common/endpoint/constants'; +import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants'; import { validateAncestry } from '../../../../common/endpoint/schema/resolver'; import { Fetcher } from './utils/fetch'; import { EndpointAppContext } from '../../types'; @@ -23,7 +23,7 @@ export function handleAncestry( try { const client = context.core.elasticsearch.legacy.client; - const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID); + const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID); const ancestorInfo = await fetcher.ancestors(ancestors); return res.ok({ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/children.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/children.ts index 83aed602c97a3..74448a324a4ec 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/children.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/children.ts @@ -6,7 +6,7 @@ import { RequestHandler, Logger } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; -import { eventsIndexPattern } from '../../../../common/endpoint/constants'; +import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants'; import { validateChildren } from '../../../../common/endpoint/schema/resolver'; import { Fetcher } from './utils/fetch'; import { EndpointAppContext } from '../../types'; @@ -22,7 +22,7 @@ export function handleChildren( } = req; try { const client = context.core.elasticsearch.legacy.client; - const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID); + const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID); return res.ok({ body: await fetcher.children(children, generations, afterChild), diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts index 5018a265cd12d..9e5c6be43f728 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts @@ -6,7 +6,7 @@ import { TypeOf } from '@kbn/config-schema'; import { RequestHandler, Logger } from 'kibana/server'; -import { eventsIndexPattern } from '../../../../common/endpoint/constants'; +import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants'; import { validateEvents } from '../../../../common/endpoint/schema/resolver'; import { Fetcher } from './utils/fetch'; import { EndpointAppContext } from '../../types'; @@ -23,7 +23,7 @@ export function handleEvents( try { const client = context.core.elasticsearch.legacy.client; - const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID); + const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID); return res.ok({ body: await fetcher.events(events, afterEvent), diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/alerts.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/alerts.ts index 013bc4302de2e..95bc612c58a1b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/alerts.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/alerts.ts @@ -15,7 +15,7 @@ import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/com export class AlertsQuery extends ResolverQuery { constructor( private readonly pagination: PaginationBuilder, - indexPattern: string, + indexPattern: string | string[], endpointID?: string ) { super(indexPattern, endpointID); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/base.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/base.ts index debb455ac728f..025d3a9420634 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/base.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/base.ts @@ -25,13 +25,16 @@ export abstract class ResolverQuery implements MSearchQuery { * we need `endpointID` for legacy data is because we don't have a cross endpoint unique identifier for process * events. Instead we use `unique_pid/ppid` and `endpointID` to uniquely identify a process event. */ - constructor(private readonly indexPattern: string, private readonly endpointID?: string) {} + constructor( + private readonly indexPattern: string | string[], + private readonly endpointID?: string + ) {} private static createIdsArray(ids: string | string[]): string[] { return Array.isArray(ids) ? ids : [ids]; } - private buildQuery(ids: string | string[]): { query: JsonObject; index: string } { + private buildQuery(ids: string | string[]): { query: JsonObject; index: string | string[] } { const idsArray = ResolverQuery.createIdsArray(ids); if (this.endpointID) { return { query: this.legacyQuery(this.endpointID, idsArray), index: legacyEventIndexPattern }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/children.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/children.ts index f357ac1a43d72..b7b1a16926a15 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/children.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/children.ts @@ -15,7 +15,7 @@ import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/com export class ChildrenQuery extends ResolverQuery { constructor( private readonly pagination: PaginationBuilder, - indexPattern: string, + indexPattern: string | string[], endpointID?: string ) { super(indexPattern, endpointID); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts index 04202cfd007f9..ec65e30d1d5d4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts @@ -15,7 +15,7 @@ import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/com export class EventsQuery extends ResolverQuery { constructor( private readonly pagination: PaginationBuilder, - indexPattern: string, + indexPattern: string | string[], endpointID?: string ) { super(indexPattern, endpointID); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree.ts index cad6d94835808..181fb8c3df3f9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree.ts @@ -6,7 +6,7 @@ import { RequestHandler, Logger } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; -import { eventsIndexPattern } from '../../../../common/endpoint/constants'; +import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants'; import { validateTree } from '../../../../common/endpoint/schema/resolver'; import { Fetcher } from './utils/fetch'; import { Tree } from './utils/tree'; @@ -34,7 +34,7 @@ export function handleTree( try { const client = context.core.elasticsearch.legacy.client; - const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID); + const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID); const [childrenNodes, ancestry, relatedEvents, relatedAlerts] = await Promise.all([ fetcher.children(children, generations, afterChild), diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts index 8d7c3d7b73158..d448649ae447b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts @@ -39,9 +39,13 @@ export class Fetcher { */ private readonly id: string, /** - * Index pattern for searching ES + * Index pattern for searching ES for events */ - private readonly indexPattern: string, + private readonly eventsIndexPattern: string, + /** + * Index pattern for searching ES for alerts + */ + private readonly alertsIndexPattern: string, /** * This is used for searching legacy events */ @@ -109,7 +113,7 @@ export class Fetcher { public async alerts(limit: number, after?: string): Promise { const query = new AlertsQuery( PaginationBuilder.createBuilder(limit, after), - this.indexPattern, + this.alertsIndexPattern, this.endpointID ); @@ -140,7 +144,7 @@ export class Fetcher { } private async getNode(entityID: string): Promise { - const query = new LifecycleQuery(this.indexPattern, this.endpointID); + const query = new LifecycleQuery(this.eventsIndexPattern, this.endpointID); const results = await query.search(this.client, entityID); if (results.length === 0) { return; @@ -172,7 +176,7 @@ export class Fetcher { return; } - const query = new LifecycleQuery(this.indexPattern, this.endpointID); + const query = new LifecycleQuery(this.eventsIndexPattern, this.endpointID); const results = await query.search(this.client, ancestors); if (results.length === 0) { @@ -210,7 +214,7 @@ export class Fetcher { private async doEvents(limit: number, after?: string) { const query = new EventsQuery( PaginationBuilder.createBuilder(limit, after), - this.indexPattern, + this.eventsIndexPattern, this.endpointID ); @@ -243,10 +247,10 @@ export class Fetcher { const childrenQuery = new ChildrenQuery( PaginationBuilder.createBuilder(limit, after), - this.indexPattern, + this.eventsIndexPattern, this.endpointID ); - const lifecycleQuery = new LifecycleQuery(this.indexPattern, this.endpointID); + const lifecycleQuery = new LifecycleQuery(this.eventsIndexPattern, this.endpointID); const { totals, results } = await childrenQuery.search(this.client, ids); if (results.length === 0) { @@ -262,7 +266,10 @@ export class Fetcher { } private async doStats(tree: Tree) { - const statsQuery = new StatsQuery(this.indexPattern, this.endpointID); + const statsQuery = new StatsQuery( + [this.eventsIndexPattern, this.alertsIndexPattern], + this.endpointID + ); const ids = tree.ids(); const res = await statsQuery.search(this.client, ids); const alerts = res.alerts; diff --git a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts index 45f6a1515d049..01c631d074c53 100644 --- a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts +++ b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts @@ -9,6 +9,7 @@ import { deleteEventsStream, deleteMetadataStream, deletePolicyStream, + deleteAlertsStream, } from '../data_stream_helper'; import { indexHostsAndAlerts } from '../../../../../plugins/security_solution/common/endpoint/index_data'; @@ -42,9 +43,10 @@ export default function ({ getService }: FtrProviderContext) { 'alerts-seed', numberOfHosts, 1, - 'metrics-endpoint.metadata-default-1', - 'metrics-endpoint.policy-default-1', - 'events-endpoint-1', + 'metrics-endpoint.metadata-default', + 'metrics-endpoint.policy-default', + 'logs-endpoint.events.process-default', + 'logs-endpoint.alerts-default', numberOfAlertsPerHost ); }); @@ -54,6 +56,7 @@ export default function ({ getService }: FtrProviderContext) { // to do it manually await Promise.all([ deleteEventsStream(getService), + deleteAlertsStream(getService), deleteMetadataStream(getService), deletePolicyStream(getService), ]); diff --git a/x-pack/test/api_integration/apis/endpoint/data_stream_helper.ts b/x-pack/test/api_integration/apis/endpoint/data_stream_helper.ts index 8ab08ff501f5b..b239ab41e41f1 100644 --- a/x-pack/test/api_integration/apis/endpoint/data_stream_helper.ts +++ b/x-pack/test/api_integration/apis/endpoint/data_stream_helper.ts @@ -8,6 +8,7 @@ import { Client } from '@elastic/elasticsearch'; import { metadataIndexPattern, eventsIndexPattern, + alertsIndexPattern, policyIndexPattern, } from '../../../../plugins/security_solution/common/endpoint/constants'; @@ -32,6 +33,10 @@ export async function deleteEventsStream(getService: (serviceName: 'es') => Clie await deleteDataStream(getService, eventsIndexPattern); } +export async function deleteAlertsStream(getService: (serviceName: 'es') => Client) { + await deleteDataStream(getService, alertsIndexPattern); +} + export async function deletePolicyStream(getService: (serviceName: 'es') => Client) { await deleteDataStream(getService, policyIndexPattern); } diff --git a/x-pack/test/api_integration/apis/endpoint/resolver.ts b/x-pack/test/api_integration/apis/endpoint/resolver.ts index c9356de07f711..67b828b8df30e 100644 --- a/x-pack/test/api_integration/apis/endpoint/resolver.ts +++ b/x-pack/test/api_integration/apis/endpoint/resolver.ts @@ -178,7 +178,11 @@ const compareArrays = ( * @param relatedEvents the related events received for a particular node * @param categories the related event info used when generating the resolver tree */ -const verifyStats = (stats: ResolverNodeStats | undefined, categories: RelatedEventInfo[]) => { +const verifyStats = ( + stats: ResolverNodeStats | undefined, + categories: RelatedEventInfo[], + relatedAlerts: number +) => { expect(stats).to.not.be(undefined); let totalExpEvents = 0; for (const cat of categories) { @@ -196,6 +200,7 @@ const verifyStats = (stats: ResolverNodeStats | undefined, categories: RelatedEv totalExpEvents += cat.count; } expect(stats?.events.total).to.be(totalExpEvents); + expect(stats?.totalAlerts); }; /** @@ -204,9 +209,13 @@ const verifyStats = (stats: ResolverNodeStats | undefined, categories: RelatedEv * @param nodes an array of lifecycle nodes that should have a stats field defined * @param categories the related event info used when generating the resolver tree */ -const verifyLifecycleStats = (nodes: LifecycleNode[], categories: RelatedEventInfo[]) => { +const verifyLifecycleStats = ( + nodes: LifecycleNode[], + categories: RelatedEventInfo[], + relatedAlerts: number +) => { for (const node of nodes) { - verifyStats(node.stats, categories); + verifyStats(node.stats, categories, relatedAlerts); } }; @@ -220,13 +229,13 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC { category: RelatedEventCategory.File, count: 1 }, { category: RelatedEventCategory.Registry, count: 1 }, ]; - + const relatedAlerts = 4; let resolverTrees: GeneratedTrees; let tree: Tree; const treeOptions: Options = { ancestors: 5, relatedEvents: relatedEventsToGen, - relatedAlerts: 4, + relatedAlerts, children: 3, generations: 2, percentTerminated: 100, @@ -697,11 +706,11 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC expect(body.children.nextChild).to.equal(null); expect(body.children.childNodes.length).to.equal(12); verifyChildren(body.children.childNodes, tree, 4, 3); - verifyLifecycleStats(body.children.childNodes, relatedEventsToGen); + verifyLifecycleStats(body.children.childNodes, relatedEventsToGen, relatedAlerts); expect(body.ancestry.nextAncestor).to.equal(null); verifyAncestry(body.ancestry.ancestors, tree, true); - verifyLifecycleStats(body.ancestry.ancestors, relatedEventsToGen); + verifyLifecycleStats(body.ancestry.ancestors, relatedEventsToGen, relatedAlerts); expect(body.relatedEvents.nextEvent).to.equal(null); compareArrays(tree.origin.relatedEvents, body.relatedEvents.events, true); @@ -710,7 +719,7 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC compareArrays(tree.origin.relatedAlerts, body.relatedAlerts.alerts, true); compareArrays(tree.origin.lifecycle, body.lifecycle, true); - verifyStats(body.stats, relatedEventsToGen); + verifyStats(body.stats, relatedEventsToGen, relatedAlerts); }); }); }); diff --git a/x-pack/test/api_integration/services/resolver.ts b/x-pack/test/api_integration/services/resolver.ts index ec0067f6a24af..7a100c37aea91 100644 --- a/x-pack/test/api_integration/services/resolver.ts +++ b/x-pack/test/api_integration/services/resolver.ts @@ -25,7 +25,8 @@ export interface Options extends TreeOptions { */ export interface GeneratedTrees { trees: Tree[]; - index: string; + eventsIndex: string; + alertsIndex: string; } export function ResolverGeneratorProvider({ getService }: FtrProviderContext) { @@ -34,27 +35,30 @@ export function ResolverGeneratorProvider({ getService }: FtrProviderContext) { return { async createTrees( options: Options, - eventsIndex: string = 'events-endpoint-1' + eventsIndex: string = 'logs-endpoint.events.process-default', + alertsIndex: string = 'logs-endpoint.alerts-default' ): Promise { const allTrees: Tree[] = []; const generator = new EndpointDocGenerator(); const numTrees = options.numTrees ?? 1; for (let j = 0; j < numTrees; j++) { const tree = generator.generateTree(options); - const body = tree.allEvents.reduce( - (array: Array>, doc) => ( - /** - * We're using data streams which require that a bulk use `create` instead of `index`. - */ - array.push({ create: { _index: eventsIndex } }, doc), array - ), - [] - ); + const body = tree.allEvents.reduce((array: Array>, doc) => { + let index = eventsIndex; + if (doc.event.kind === 'alert') { + index = alertsIndex; + } + /** + * We're using data streams which require that a bulk use `create` instead of `index`. + */ + array.push({ create: { _index: index } }, doc); + return array; + }, []); // force a refresh here otherwise the documents might not be available when the tests search for them await client.bulk({ body, refresh: 'true' }); allTrees.push(tree); } - return { trees: allTrees, index: eventsIndex }; + return { trees: allTrees, eventsIndex, alertsIndex }; }, async deleteTrees(trees: GeneratedTrees) { /** @@ -63,7 +67,14 @@ export function ResolverGeneratorProvider({ getService }: FtrProviderContext) { * need to do raw requests here. Delete a data stream is slightly different than that of a regular index which * is why we're using _data_stream here. */ - await client.transport.request({ method: 'DELETE', path: `_data_stream/${trees.index}` }); + await client.transport.request({ + method: 'DELETE', + path: `_data_stream/${trees.eventsIndex}`, + }); + await client.transport.request({ + method: 'DELETE', + path: `_data_stream/${trees.alertsIndex}`, + }); }, }; } diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/data.json.gz b/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/data.json.gz index feb2af93b0fd18126b4c783198f6350f15323c30..3f72463627f9d85f7b927fe2ddaf267e5ad17559 100644 GIT binary patch literal 16685 zcmb8VWmpt!^FFL}2}=k|cY}2Il1rCJNh94L-674=4FXFeT_W9`0*Xi@Al(h`qWAqA z&+i}a@xFZE16;#$&CGSqdCm-Z6beds|DM4!cr#;H<5$)$4))LXvvu5w1!_Mo-@ZB8 z{=CxPem1l1r9Z9=W>m951(%iC#O@ zEm0_2yk`$niI^mD8Ba?Jxvyq1nOJw!`m$wEVOg*~g_u+k7w%mKS=Xjw6``LqDXbB? zUj6ZQ-idQRChS`4I<}3AK1!iuS&Zps=XbDm6U6MG!?-iMal^YJjM-Y(px`v}(sL4pWQQ&Tr2K^bxwVL`w^uxfj@93U)F= zt`qY}H81a9`pL|_PVRvCz(FPuGY zvkPX!!c?YTcjOvgzA4$B8mm9$Q2ux>gooS0G*`Q3kB_+dr2%gK{d8TjdWG+I-I^A` zX$O;s_Nf2`T3sti)!?PQ1KTATK2y#p(1qt?`}Je}-Q}g&{X?9%Vn?vEDg&x)KdhS0-i@g-0_s!l4kPk>H1@r{w z5g~nkZ@t6tMZ5$L?9wCoBnx^!P~dcUVB+D6{D|m zE{aCnqybk3ohZrUJ{q0LkrxGkiK4dCyzIA1Yr0$;sb8E4rY6Qy(9R#mrPneeaP{URp-kF90O%VlHfXzwi^!5+EhrA_YIE zDYxTzLKRrUWrz4&RG<%0C)IYL0SXs78|3_}Mrm5!qX->;k&30^D5y6r2xEM`dehHC zyD)~=aqmTA0;JQ-RWN;{lXWm(_L9TR2(wjB$fD>pZ~vR}Aup+whytk5f+H5B!K@Z=QC{U)&`s~WSjHNm4K zbY8dQ7P9-w@4-j#%42`I??_Vc-aoXXS= zhS}$pd@up3@IUt$m2qet8DVEt^Ddeq(lnICfKmv%gza z+{E2p{v>fkpoQZj;yBzFCbJ^^C8xR?U5$O3?69_gg%#9%zgDx;t>5 zI??tzwCq!WA4UwZkt?sO^XfU-88J@czDftj@#Rfo?F!a#ZD^? z_1iurKU{lf7pP~DB&OF^)w{VdPxP|SlAp;Xr+c`)@T9OfEOl_vhXna>w>Rm_ky`9D zIVzVjZSTjgd32kW(z8d&j0SC83Hdq+WZ-|4F0tEi5#8Dif!<}>bQj0`Z)cjZ#%BtdZdlLWgp-E zHV)ztnq3z>b-y*fKj0tra;h>Ro=;Wxi!Pq2$QM)bf*1OHo7AztRhUH2?0#=>yT4qH z#xXw~7fulvZlG}?ohzp2vU2l5&*2UpeynJ#UGbJ$V+i~* z2p}i5P2T(xb*F@(8!t=V*rdC42|ju7>&g1`=D3*t4$2pG1YmY~`OdoUU?rKW1`I!@ zjur{DeRl(~SLMDylTF zs?#vUC54izG>Hjm1c9xUp>?}e#sE^ZPO!h)r;H4*u;(}pvbAy)^Ep? zV~M&)XD>^r!UEFzIr$jzr;^}*oW|D7)IjboPJ>{gqW2}3=Xt=O@%7L=a`YIWzaz)( zMOab=ZRfYPM}E#8o>@7t1a{K`^#E`TiILHQS69y@=jrr7U>AIwa<}32u2}n8q-{TW zWCNRnz~sVfqgW{w!29^%BOo=sjueFux@No_1LK@QMcXNjO~=RJc11HzTJIL*%ryQm zZ*e|&zyvzRte=tG5vI{3g&NvukT(VCw3;;@11}sgVY2bRt~WGLOlbX_(&PX@7J&2k*E*i96wN!>AMRgW)BT5s|ym%xHj2 zd2n1!ZoS7pr(aYPB-sZfyTZ#V@pa0#1g_C{D2iPR6%v0th;QQm*}48adK}vsr#d0= z;|9$)Z~lcdjC3piyI~*>&S11=H63zm*C~w*XbbsglJZw6-ITLbzt2q99(UH=uUgh~ zND-PWWHol*@ged9ffcKSoYgmT@RBG@1wC-aq$l{ML-Ct)R5lpqGW@?@>*VVNs3rT5 z0PoWQzp^l7->|_0Ura!rl6NJ%U=#FF4mx7v2*)-*@p}RxI?NQ9)j$p$_oP)n8$?;fPBowi4i+Cil}d2dysjN?dRD-69RlpY0{D-hq?Ce)Z}fY!V(Gp zj-QS_Bi6VzFy=!BLcZyVTrmblJ~!tg3e@aGAJ_I1%D<#Rf7hN(Kbozh6~!nVb{Pkw zb;>%QdRYA zGB!)}DtZ00zUull2gsztgZ z$#)vL9E_>KFf%pBK&H}C)6%P#p)afK3qx%M4UX~WoMr6bF?b+&Xj=_g*!>ja)YeHw zn%=`heqW@~n3w374ZcyK&z@05UG(zaity7zw-VSt~lvi1$iTZi%9@`DBuW>02XOi16uZsC5CA- zqrEeBc3h+Pv6P=;WU>e&Nv6=go8f7z)Tit>P3J!=xG8zIPW!B2Cx&YOdA~ANk2RsM zCK95#xJQP@p}<}|pG>@25N0jS9!qD5e7m60Hck&e9x%~kDRaE>GccoU!wrRkt0$a3 z9pEDszZIsjPc!SS48=FmB!6krieSP`+G{y69F_el41-4695u_MY9+{ADhK$V(qOhH zj0c>Jch00#!HvL-reM<1ay)>Tisl8>V1LYyPUUJuXZ=NMNXn`>gB^V_Nu}sjo>7~n zk*sZLg@Kc@CGc9+nYF9W=!hvrYP5j`83oS(=2CJt`Mb5N@<#rq5SoZ+49Y{+;M|Xm z5DY~wLXme&FaLFDtYJMp&-lS>Jt_uf5NJ*vAle^*QH&8diev!t?w9YZdwT!BovqTJ ziqiA`N^DeKef0yL1ohxxS6L`Ntj5Vkja^B5DC@E_ z3OFFMzW>$vF^J{>7ao+hX8?%ID#Aw1tl&^%d~?x2U^J>AN{QXS|FXS4ame$s{Ti$B zqHb(kvgTrw^+OKphDcOUV2rFuXBdfWzejW{K`f^Ne`2e)cg@9kVudg7*NxrnMXRP_ z@9KyM(`ICg%&8Ash)=2p_&&+9NHt&Z1M_DP9e<1&0F@~aG511<2?|TbZ9BO2#kZpg zzg5^S$2xSLNJb5zuZMxr_659TO&U}IpEh;k8A=lMn79Wax0ZoSyd|N^UtH^7ab9ej zYxb7BM3DKfTBo`*%7>_5YAJ?xY`Y*tzVLmPrjkd}?=!PI_Who1sp{dOJ_2=4Y*=<~ zn4h$c^O#CyQC-?HykBI4O~+&P&16^ee4NGSj4~pVtl%l^R`oKQ;-5-^ND`V*1#Tmy z&?HQHEQ*sPE9UV2F2+}bEoL*7v@aPd@RPXW>=LxPx!zW5$^9?FhG(xf(A1$iIRt^O zU}i{4*0VN^9+6K^TH8^vE-9osO`?Fd$ch_J!Nf+U-hdVUN5^v;{!?F}>hn(Ry6gAeb>9wOpSQoNCx2}hIiFc76ftv9Lf)ufzJu%iVR`jKFKL`9e;uXb zuQ0YWgeA_%<3{J1rTG^+oi`Rgx-qyC0u! z&2$aeJic1$f2|(V#88!er7z%@w?N;-48P|27V{rvgC<%__y0@TWK3*&{ZTd_#xFz) zinET$Rc3eIw?8gqx(SgxyZ<205ewO3eHCzyel8d|eD-FRji>snNJ!|rIW}aWx*2UZ zic7Xufwh&`5-E%X7K?_dG)jdUN#Vd3;AWJ@FJplv;{T;?enwdbneXqrxWBHkX3irw zT}L9!evz&3(T0BC-)nX|r#F&+WfBY+m~70x7OgD zL}Rl0b={|0(Fk>$XkFd~k1@;C0IG5xt$`UKMt4t|bd~6R$Vt1ycZ1v!t&u~YMvp?C zlB{U^FqdPohl}Hx4;@urtP1w`*Le=w>~T(%yT6H$*X#5rEE=kY$nE{!949V^B|fF5lB39^J*b!;`p5zh1WbB4+24$-e*Bg=~5W6L|mz zGsjxm2Cfl}v=(j|6dDpLAB{la4MH`;+L;v?Z^1MkN>U z)Eqq|r}&T?jQ8wXgeQ$;@M%z z27n60;V*8T7y^pu9=fKi}$81oPJKX!7TeS})uelM-;`eJ>ljQd&0Gs#ideO|2MK z6;PxY=Ah4(xcZ|%KXd9}*CK~3@zU$AQ(F^Zhgu~ZdjdNSO%MZzL7i6y1GAjnYg0V> zC1BY7=GGq5U5Xzc!Gt9Ew)B%^k+T@<>LSfPi_KAMHTTZU)M!!s&m?Y~%o28tLI#w< z)bjLStd&Gax1(Ng)MW#=<^{`}_UX2+ZJMgv4PD6#dsU@=vgsZJXNlhC%d?6 zMOVd1gs;mf+VSFc%<lc>5U{B40+yAopx*m&O=6Xdw)6 zd~WUh{R_UR-rLp&ao7`}17%>E`LKl~fzOrGH4W{GI7|HuQ_a;$TT-2bZyub3smHA9 zuxYia$)EwEg8bX?jL&a1wIfp-I`ro(6xvUdZX#LC<$axwj~LIBZ)}f~yqq%hR=E9( z!58}n*Lvw@%S6lDB*qE8h$7|*zUyAFAMoOzb1m} z>9cO;@_2lGD%w&NO_vd!bTej@biU2!C{q{R>j%|wGb7@1J&Cqi_2iT}a_cNFIxtxni`CWHV37&k`aPmXcIFEBT8w;$+M6Zw!2;N88(7(mSUjh`mc0{fLI&= zC>pT`dPMY%izVT!`dSbq2vWv~B|bQsnAck1uGWeX!cB_q2`@w+6EQ`U+9YzECA0YJ zT3?5>UID{+iv0_Z(}Z#eOv!lRd9}7)FJdWukXY(00;kMVF=I4Co{vs(sCs-_DYguG zneu&5F;6yc;Q5({F*bxeGd7?#@IK)yZJ(0apr&jZLN$0SM#8|;iu+=G_dbj*lq{5t zjq~c*bfzorqJoyV|NEGw^JlBHAd^=GxZ+s*5{IEODNGS1&i%$zo@lgN3KD~@PyaYN zar@mj*P{Xvl%2~(Gq>_?PQS7k97L0m+wEPp&yi-=++2scd@ zqUrsS?=4`L1N%8O_2-c1(U3uT>UhmqM=}A*A5E47ev1T*cOD{lSLEihqfXb@CCK{x z(Fm#tPPUBGA)X=0sZ{dxX^b}C(Zzl#eTVZ)Vi?T3LrGlvt%fjy3j9>)Bt_X5j2vVc zThBqV3SbZDlaD19Mr;8<0UMsE>hF6vtsNDS5S#Rm!SaLg--z;LGmWB;CX7a`-R*!?6Avu z18QC9`T@pWt8xs2z_z1KcFukqRiA9zvuU*ND^BN62|N)6DWay_ml&kUxh$%)!%(|M zpqKy~C?io5CXzTyc|#*EBY$s$80_VYVcym2gy5hH9M~#;?XtoA+W9SL1H%VEpdlqN z+g238VKEZ6*d6Do(HKCkBnw)oEA|%>QxDGx>xH~zRKOG?#Gyr#*Od*zj7XBRULAnN zYGnZZ2llDP5UTUuXCS-`Bh~yM2QoEKnXxv7QR-8}0y_3bDl`c+iLxTw=MovQLG`&5 zEJT|xspSAw#u)H)R`9%oR@hX9)A8C`NiTy&GiwnMtOSv!$%6K>#(041X_n%#su9gA zI;#>@9KVjlawc|Mp6>iwqwyIfEr#>icm0aAz*HWFgbgDSQxRu=4w{gLEC3G8D_CN{ zpkP;a@NqFZ3{q^U{(^@>G!x4AILx$qYv zNsAg1cWNp|uR)D1Xa-~kMsG=nTZ@6J>-YoG21E8_X@144$!wHZs&{cPjo8Xd4CX)O z`X0{67ai_m(mspQ%5FFevfoAsxIjKtW9|^3fRnI}*pc9hZF%vAO1%w(0NLgn?)tg9C3GNxGHLc_AZw(qvl;_g6Q-}h}P_Ho8H2vteH z7p&YhK1u+R1?h;Yi$L-p5(y%_hU;HSt*_t_n7ysFnop2N{e+)X*E;%I7j?q5OP8|d zYd1QMDHF(?1b6MbS=UfA)fCWgjd1?2MTrMLE0TBCJI z@h3Rzz3hGUrmj-%w=XXGR*wcyIjS`&4p0)oNR`NFFGkd<9>&D!0GqN=y zo_iWW>gtCuo96eRKU1$Ew)kLx{4!m)JoX6-S+`i8ssiT9(10bh~A90TD>#iAh zY>%ME?}Kg-<3BSIuh?7npL+$Rro4b4JS}#NDMkv@bV-B(iX@KMgir7Zku~S&!D`LF z2i{_NFV2_DRaLfm9F#IC+J&B)YA*)l*X1RmSFxz;bQ#y)@9>d582GE>-%02+Iy!>8 z%-BrnJB;Z4t55c5kJ?|Bit4b{1uV#y-hJ27$wesP5oRT}YkgOxz#BhHRGx?`T>W-Q zP6pRh2-CYVXTsVrQEc(9a*ew~t@yLRg>aV$veMZbuH9b;)ucA{?vg|8m3z6#vW7k50Irsj&? zH!>kT8t@;`T2YZ3|O86vsxgsp8pr3m-bZ?mBiRXd|kb!lrx=HfR>UY;~XG{hsp0|Myb21%7Y%K)(kB+-B{zkS7 zXEd2s{vHbIL#vZCoeA{B*`BWr_kQZD(Iki+prd+NOi`-=2;2y^Jp> z?9G`x|0=ko^c++6iNcUHkyG~SjGyeap1}3jV~5r0(gZ^_l-Ubs+UWyc!Vux>say*g zeQM(Ng_8KSB8rtqLQLWXH4R}(NdhKzKFeAc5%d#=oSX`EYJ`q@%FWA=(M%Yxuynzt zPZU4AI0sqp2~dF^=8kBHi($~iuBBm(6B!X`90`TEuuzUNyr1J-*ZF>-4d8Sbh6iwd z7J4l6o?ky`C&q*s(t(I#LmlV*LTGkHREDTYbr9HkQ73&=A6Js$OZVw(;Ab0q%0Caf(NXrLs7Ngn%T{klB#kk{T&1`r^&+h6k9uOxg{K< zeL9JpDvkK5BS6YTTS0N$83`VF6tJVL;&L-ikP8a4CSs7b`T}EMQ18bpZ6_!p@1PBi zhhiAEMi^%(j1SsAmz3w;_y%UcH45T{izK5(WJ8eQ3e&@sXQKfoyALM^^P@>TF@zvn z1j&}-2{0tnj+75I*n=MLLP}r(mIk^8oH2k8@JHkeo)dH@Zj{bfX36UVYOh>SK>Y{h zU-~P)wD4b}qLkKbrJ;NeHx=drx!z(^;Y15#^n}wLcA;bASjEriYUJh*VCWeD)c-~} zp_1kRy+Wo(1Y6y7f&+Wnm15nS5(@SIoph#BKBhS>_H*7G{CFn3lgqD3+XY0T$=d28 zq@mnTl2J|*FLmdL{PbnN!|;awZB^i5)JA7iR*jzS!cZ!J%aPs}zi>ojFMTXbD!$DWI$1##Zg-eUyl)6Ej+w1!VS7xf5{Q zHI%PPq==&zXfE+jZkq9uS7oh4#_2s4c$0Xv6Br?;mP;WT_)2=KbFf)=mMXfLgQ*23VZqK z^8Q*$wnC0c(_i57-eQ{lYcxpwO~5K2six}^K;oLMAPeCl(bN+HnE^&aIH@)MUhCxi z_ngXy_~mJWK*-}ySC)+zds)$7x<0UYkaMWVM*(cwI5{SKoZ@)o*RhEID6}0FZ%NOQ zjYB51Odf?;s^)@?+bUDWxciX4@3i~5iF>H6#1_@C#4aB>s2wJO_5HL{21a2 zd-d#3aRZ)oYt=nYpHV@YIsutmr4ijIHb@r?6-Z-q;<&}7DJ<58MULo__)6C|geNt7 z;Q~pNuQ{8Q$fb@~s4ZJGkZh-pWGX&iE}}TF&*)8T0pnfTBja63WV@i>oedY@ zL@YOvTv7hhLRZ{RV&~90d4*5}Wur88B49rtGHo-*vx2dp=0A^XGdsoC$J^Jb3h?2_ z<2zu%x5twd$+#$!VImeFRd+BtrKh+IIL^!&;e#A2mbsEu%_GbiYam4=UoN7zQ9nf9 zhATOe^o4%IFcoG4Y2IQ7i6j%rA{79XB@SsN`vsze!3(GZCP}ocs{aV$eaQLw{Eq9& zrIcKZ$bi(~JG^yI?0i@wq$=4-el;sgHAsLikOdn}x}sH8OQ<0u751cWVCeDEmxQvQ z^1&-s7Ps~A;SSa;3Wq$-{6TXX&5RQz#y3niUGSYknX&;L9WL0TUV=pL{fKT}+Unb5H}$TXvn zCIpd8Co8RJN|hy|kY1?V+^OU${qV<5eX7^etMM*_@*so3U-4e2E+75(FW%KGm;d&C zYbjQ?fLFz*;f_=MQ%%hFv(^m#g?-;IzP|sOBS7`J0uKMzIRe+@mX8T?Hn{{(QLflL z_x{%juX*gsFIU&6J1grAhC(=^{xkpA^{DEZUgm!4`m~gInG7mA(XWZYK0Omnxd%?Z zwY`01ZgH~rIURaii;KUOXEfl8?w|X9B=V}cyu!=QagIInmQfK{70W4`+4p#C;M;vK zx`EluC+v?hp_c`>n>tV-X?|1wJC#WLw`dQD>mNPP5_e0mv|Zw<%8RH3NS>mu^ikFt zMy-t>>;!@%?cV091vdH_v!lJaZ{9VI6C#=YI=1j*>!8eJRtqH7-~WPBkXZ2Z(Xe{c zOFFF&)BZLE|Ex$MhAH6vvmzCooL)q9{*N4>`03rcqa*YcU;kG*Akd*U@kb8G=3ZjWjiF);w@CgZ5B}QY zmrj@NY1Nyx`~Q$7JA}^Ww9wuczoqA}W2|;a(73?0Z?0 z9u__OKn-0@=Z#%{2IA)7*9lCrKy%tcoeZXt!XOCYw^OA0PXhyW_m&$I{*?g|j09P& zL+?>m(HQb@B(z%gLK!}|))Qf~u*?o;YR=uh3}Ja{E^crAp^(SG|J>1KutXmjg);ppCrQIZ`pSia^AbUxH!?;kAP&{I>WlABq=_qu&uNl%A(Erx(7I@y4&sje&+W$5tSNCPZF?n@N zsZBCAxSg3OQ{!lr=7gA`Ta-z(!GHR*2$X!(@%HB%@0VlHc`itBDU}95Qn%P^_3O&) z5wP`&49YNSYDSq+tAmn4sXk|@N}eowKl|f%SKtQgURG};$0+}>ga*LC8|tLaU{$6` z!GEI}i_3Cm8F+Y{t0XJFz=c(}PJubXkjP1~4-Kh7=O@$U3KMoelBeW+of3rS_v9zP z)@skiM_a+WDKbCdB%$#u@dk0D&}$U-GRQj$U%2qA6?M7%ja-(0s?S^yNiL-*?QiJd z+Pg4zWFiM1>292fq;URT97$H#nay2`K?fySiG?1>{yNYON2ai%w;~%6Oma_w$r@8HL2280xRT@y1v@g-0-++4)Zsoc_@Vc_HH8s$=8In^T#6U87~Ki zTYr$x06rOw_O6BE!6eR~PArVb0lKr)1adOs;1gH8`J=g!g{Nb$l6i^xzffj;p3Gssj8EzT&0SG%oqjNZM{)$Vy35(V6KWvDb z6wL|?)L5lRE5QhP6S!{7!qBELSW1J9dnBJWS>gz0*Sz9z7{)&;Kmf&($}5eptq@8F z(Z)49ViIohID6_;#x0=htx-H(#pfQuNohc0Au-?fa#*vV;X*!mbm>=eDa$Xe1Zs5} zor)xYVzTeUlK>SCpk!ki%d-&#JjvSVLUol-wcS*#tK0*PriU*DrHU+7WS}hb++O8~@w&Eed{$ccl?ZBich9ncAM~coN#p zxMt2up|A-!dd0lJpAyy{HWZWaK@B5u7wn&As97hw_R+;?!l4ig<-#*L|sI!p{<(Y<^ZcPe>pmQahKW=FdgD78UCfYQ_4sh%BEmrxLQJRo$- zlBFh-Mwvf^$yA2IOen=95#Ga()}S4su_cZ>vykmH)K!dpe5=6jh4@*x-vq%$6xNXO zEEpvwUq{d^kmSh2>XB+Mh^sYwFc)%4=vc#=GLyQx%D2F|_o^cPP+;W3uC7|$x+#vw z9<1TW9kA5$tXn9ETMcJ*o7QQXP?=cYP*-)*1W&P;fG?0ygmiC5+m>?!7%TmaBu$u2 zN%PitrGY1clR{)o=Fa?S^>k` zR_>|&VpQ$I6r8U@GVuUDohd}ELGLb4K+ZPZmM)r-t|IP_&5bqAZ_Holm8^k+bV?bR zUOsFYA@UaOJ6rP7w`$=QGfUT#Z6e)xZ_xzC|R6yh5|fEti4Pm;z(EUb~`k9I5Hh;3;p1BbR z4P!C7ud^@I^1r^j-WuBef1z>y1P<#=rTuzI^U6MzBps^zi9eEkxKCS1q6xg-uMuJ< z85c*qiI%nGV5N@XmR9uXG%KXz6?ertNThwG^~qcK_vPJB!L47sk2yI+w%?G?06h?%T!6t#2a|UL9_Q?3WQAWP19azl zLAB#!2tENrW#-r(WgcCK_c-KgC5)C+2C2k zebqJVmhQ(-F@8J-XWY#Xt8>JUo!SjMIs!8fgQ@UA5Hr&5jNstktsp`0w%rK_U0Pj< zWiT9mqPJ@!Tg~{islJ*vmq!TD;g2qRn9^+dS&*(6y6-=o>oTet}i)`#x)QUuyY4s>{ zLN5C9bf3CD&h|5eHPhi)kupSrgo3~ujYBTz=t^ixybeBO8BiQ{0YN1noN-HHRXI)S zgDzbq2>Ya(rZqnIullaUq<;?5W!UyBj_0~@%Fb!cwb^IvE!lySjb0)3CXJ&V1kh>9 z;k||mB8_P?a5HiXASu}oBJR7fDZ1YcY`M+68!h-~;Yg+f$@Iu$L6*=`$-%aY-2*Fj0ZzBHO%?fN% zPz>%#bS8n_ai|PXcrG`xE#XlFsfjKy)1mqXXKkCF#QY};nk}s@SDd#Q7f*MTg@T(_ z!2-$)D!9cS;UG05IU3I{h?Ew3l)9NF&d92+4!fqMGv4yX8c~nC(zq;JFx?B4VW$PZ z3{_GDo>(gS;6d35E7ivmh@zu3HmMzhl{w6j61ZMM)$h3PICl@hi5VI$iW%G_d@yam z9k1AHDPg8pm}-m^9e^Giejj3xKP+VR7Bwv!Qtw@s2!D!$e+>YHO5%wXfQ=+5O8DLfgTz`zF=GzTNrU_;aoDjJ$Owm)}uES(MWC!wcIR@1J7P!Vp} z>O`+S@isq6A?gwp3oAqO^V9kW?B3S?cw`Hn4pLctN4Bx3fU;3SYn3=M`2U}Atk zfD(r)Xa^7XWpApq4I({j?~u99J7=G)6}f1Qxo6-L?GN68S&Aea3iZ8Vq2g{`;4bj3 z)~C0;qyko0%8j{|CbGA_-uWiA&&VWfTS>l(>g7(Pv8$gRoYj28#>U>->ez% zVaj&B`}zl>@@{RYzNv}oC4+6Sw<4+R6o(boPl<5sKC#70OLTFlI@(mVkES`jZz(pd z8G;n><7cYHZ52jXiz!u@h?1)WX(s!qaR!r|vTJoMF~>#7H`f9Txlv|~pT9{SU44cl3zp0qF5HPH{nyl*m@SwcKY=1eoJbWFg zM@^n>npLG;tSVsWwC)A__CvK{2MjBKovF!`T3=X8L-Z#xcYJ8QaBnL`lX~$-9l6{xTCcvRD z7+L6gV%!@ke(A9jq5O|GoR-r^P<>IuntkqLdFejXXxF64aCB9Bf>ud&bxIUIGNCt? z>Xyz384S0xB~`(dg#QIoLtD&^A#_4bj$6V2E$3jkH<&Vl&v42SWy!*X66jEo^k9~M zQk3s0MeSGn1wTW9X~p5F4;*MZ5}g>@W;um`21Zm$7@wC*RD(gx#iW_L7a@CPc(6D) zJb)kZ{=KLsDo=xEkjXn8*nOWe*>w^2U_B{fisq%{$+4BM89eQbN_tqNG3o6K>eEUR zZYx)yG5m~yiR)Nyz|?W(FvVekvJiy(_nK5;V8F1)UI81d##&J-@xs&3Pk?dhUMBB< z92y6cvaogyp+SVCdTH;>oFh@!OpYR~4t%~h!N39|Bf4krz-02W?T1)3UE1ZM7GkWq3m6T&s7pY+@*JI*mylhU+PHCawneYAm9mY*PlB$Q4W3pwd0=9ix$ zR)19y;Tg7CxWS<_|K4vbG*&#%X?&9%%Jug&oZuqN2*)N^S&6rJ)7BXIAtPeyFr=a=RE(m&1X+|SBOYrJ0UB$QDJs1Z~>d<(7M9_$Lo!5$Bd(KLGd{%wl# z?c@{ZBjgu5-&=UQxc41>_TS1}_op(C5))RNg=3pyUy@PF8Z{H6Hh`R0LtZ(ZJ>@TS zq<>37$z#F&*ys=W=&5tgs?VkCLLGiiH3)|1we}D_d#-@Tx~c1OCC~>-1SL{W(kNN* zt@>E9U5K2mro*znf~0uAIHEHQ+as8zX^fbdg;UrH5BNfg&bqbp3GxTD{pGxcwQ4ChY&r!wcgDoUHtz-B^S!!+p8UWZVQs^z&sz3p2<%Gf9vDLkiwv%oWyxJ!Nz z{$g!S`8;v;L0;?lQv-^;>|%W+$FYkal&_zPpyv+RpIYTXBUCrs%V5V&MlBs8F095L zWGP@W4NQTX0}XMu|H;*i*p&JYbYJR*EssPyFOLRI09idK$b^;b`HM4y7>V4;rCC9V z_-kgm-L$e3Ot{TQP-id*VKoMAP`w*+L}}>|o}x>TftOJap;H=AT#!se#<2vU3%ygL z5oObLL4XyqgcN?<5-O&YX!lS$J17`6i$p%r`gA5NP*(}nf=1S$i*&UYXx0{PR7k}L bFB(ZR8h3}_!QI_mf;8?P+{1-)&iC%0H{Scv zyL;@tYDukGvu5pH#F21t?#!WjV2~z;&W4Ou-|g+dPP4S_cQ|YPHx9nxc-WbFbdAql z-W~NIF}0ajuSNE3nxOqru1{L3`Bt0YjPlg=1+W@U&x36~ciz7x5-3V54X8aft`SxC z`#XI?IKi*5bvU%Hl=@KMb{inWlm1)cuk2f)U!-Er>*-(qL4o@>yuaVVH~jdXZ({@A ztjYCcbfB7lg>0fTntcw8WXi(C{wk`DzF?yFv}*m6{Ov2tw z4DEobMf-VL;h0C}b>&?r5gvPUrqslo6XiLl?Yy!TEBZw2RM;sfp+Kd8=4!)IUI&SW z6eJ6a6mEqEFO{Y;R=NoYi$)WS?PzmwsRTuaW}#&D}6K}-i{j=j}!3u1p; zSQrb!B^VU$F-^V>-98eA)=q=6i^|`(iMJ!To*}4W;*eOHZw$+OT)i2cTv&k^)&-PiFo1i0Xq%W;8kb;_P0Q$&E{S$-bp+Q4#riV>I zJ?;@<}&-Y$`Nb z^VQwyN}CHe(G~PelP2!sG+SHWne#aXILX7wbrdV0_hj50>cT76gj2=fqs!%Mxgzm9 z-mSa@MQG!mm!t36dh4$ru5roD6 zmVNh${S@*^P=&aAV~79vo=f71ufRwCLBVHX1Nn9%a=H3Gk5a{vxjA-3`^o1dW#45L zyQz6L1|d0gnV;}6dZ9L`xutLgUdyDY1Liv?3e z#BUT)cLbidkax$B*7tfs7wUyvvq_qPwsK|sjcUIogvx?S)C%Ny_}jKIG^=XZvto+c z=@3dU`NFWo9&4 z)iLDdt2}wEcS(EN+;4EQd7(7gd5zmgQ*y865y;VO+O%bv;E8%BrC3uafE98k4pL{U znXhnD`>EHd<2G$85^i}7GC5(xKCoEzGH=XW>jN(D60mn;QyOcthW@!%==duj@cQES z_OLfce4>yp2^Umr_e1G8*;)n=Z|R#eF1Pb7M+HBYCr?3BhcXi>_t;&r3IlcFNoa4( zsP*{q4SY@s&-}*5-ZHgg-?d|)uHz&*G;)Q}PS$4UMS3iDy2=XUwUU0Y@#UqobH$#H zA$C#P$M*HU$|Xoc<48gjC@AU{<+FS+m7Ma+zK)T(YVok6Vdh}YR3Km#wh8Oyi+GBc zM~*470PN>1RZ_SBs6zPwZL~NTF|?mF@t81V`)77k2f6A$z6-Q*HZMMZDuT0={I$Q( zM>wRrw*M1Cp}3!a-5l^uz+D*Z+8i_Qyg)sw|9? zgmnF%tIWiuq2~(72tfMHpU@o|iD0skL_Pqx<4+2wL7CmOEg{R~Y#9?#6VV&J9S6t@ zavDd$ux9_|g+ks9c4nA%WEPK1XByyiKUrlDnV78;AHFD(M{^v_jASRj^lqVO@$hRR zoN0|6k<40}vAhFpm;q&iu1aWT9TJ*Ukt!-Z2CoR2Nd7$@snA_~0w;ka2v^!5oG=ED zys}yW5_voRf-BUYI)G_lN1@!-;}b%LM4g{53CPq#ZHS{`r- zV@)Vb#axk~*q8qh1c0r{(t1Nmm-H0}%Q_0;VMt;^*3qB>ku8CE&=Jwy;(;vmDr748 z_tesd*L5tT!K&PJx#uixI2CWNaSg zxw7FQyX5f*zF6TsOe<`q)4gNgm5shggun3P8GG4^+C7F!u^82xJzRvhBb5mEurCecM#imaPoOehXG-561hwQnQs{zi2(L}!*d3<4wItv z|1(S5GpDGHzj(5H!B;XO_JE&!kCjOe0GYc<@SiZ5FTfrll06R!C9X2>?C6Yl4D*wH zVUA8Q#)GNT6=+<*D|=7M4|-SVFMnv_U_Qs=;{vyNNn*0(f2D^K#wp=iS#ZXg@0dxm z#-pe}h#N_pnVajsjOF&fTWtTKL9>a#!vS-;B?1GL6t*j3w&3_{l?raY)Kq@p+E9Ah z+Rww)hd<-=38o`y+Ff4y6!7H|X7NqPh;(oy#xO1=3>o(ixJS7MRA4k)8hRRf^lo1w zaFUac&Do*z9Zc}i2ZbL)*&Dp(PzG3p5glo$1X4#MaX`@E^8JR=rW5{Pj0>eirW8+& z;JTy7E`2D#e+I;Lwd(_{B>Otbdd!09xSAB7zbP!HhZ4*DLH_2y}Mq< zKD9R+5mU8e7EMF4vzSSlJ)ITCQF^tK_+`RhL()XBeY{20hWbnH7q%=;@Ga;au`xr6a0I(Lc-_dXz$&c$XrBWNwX9hJq`2}&Ok@AzJ>(F zD2tDMC1D}aTLZzh#1aI%LtfK&pg^dAi8e@g7H5gb%R~hCz+-kdV=zLJL2ft5MFPD> zgxUjWk^{NNf_H|+g^s`B;w_^xATTNd*j(uM;oe&)P{85puCGu<;=uIcZ0!T0m6-nQ($1CR&k^= zXG6{g8+;}UKf?V_&-Np(R>@xgm@i?dw>}Mx7i|VW4W4+YBH_w1Ls*u?QA(a%1Ia(| z<&!wvrsWy|QV8FFw#;aeffR)mN39f~%NfK$=GrGmz`>6@jlX<3I*bYn*)10dUY1ec zc0ffNHCp3UuMz@aY2gJ0@7-EYn=EVnGzct`sDWbo&2o{KL;8_~BzH64m#0#!>SzZa zj*g2f+>uK6`k90bH`NTC*NoOo+C=(B4*>YcKAEqk=h@1Dz*;g<69&0aNGKCdQ|cp| zSrLmVjY*x4xURCprmaiK?B7z{{sj)puA_X;7Oz~a*tdHExVPYOiWr)yPq!!6-M z;i<;qFiYKNl2MU0@o=05p~c%>6C-8AWy9CY3q|E+oFjPm03~0AEvqaMi1Etruq?4Y zLG*+-K?L1C0xZPe^}rKqOStAr`ymno_6e=YgFrKw?*S}l~EZ`mIL zoC-CS^=X%vT5Al=IY(Zu%>GW=N%`sH z+`+!CJHO}NT3<&#<&aB*x7bfNNZOE^EEpi(-483fXk`?{&r#2>g=VzYR-B-;CE&Kz z_~-Gy-oDz9cp6sAFZh`6yudf7nCR~Rr_f2Hotw(=(=xF90WC9TlY{JPMrYv}odLT} zpzR=`N&zFKet}-VERt;W!(Eo2Vs2!&Z8aZ;C(!B4X-6tGcJo&TAAt(1y)?Ii{=L$t z@<#M0Qg%Dw%<^=3NZac^14aR%T9e|-N~cQYLr1Gkz!pNtOtwgsZ?~S0!Pz?9@md6{ zL@SF-ZKc%0P~-6vwX_RPgH^rdkmWajvJ@gJJZ=Zv+sTy2}p9gP8jV~sbh@>zuVy1)h<%FfdSr;_gCUB-eAq~}$bhdi zIX!XVLp%6ORqXcgie>F_pxnB`ZX1z9n-?s7U^%5;Y{;G6wXdl0<;KzBW-3l{C zY!`b*K(p*nU0Dmc^Li4u%0RUHVK&dFH2ngyC=NYq_IAO2g5t-}v`_54Kg)b9iOl+r zY`y>R%SmwJ;WqbZdBKiV;a9AmeaTQLmMY?P^K5o`ZA>DbooS!l(KqX>$6*s@*OP$^<&65%(HCbnVvcRz-hREBR))d% z?b{KE$_)%-gI4o1hP~-F|8xmlfj^`qMBwx>FXliu?r6Pol(OdHJ{E@l`eT^!f?1mU z&A{W)NH+K`J3ew;6#ojXCLc7w`Hd#7WiJ~RR%kRT`kp9>n+}fk>y$}jVsgA%UjNmk zL}n#r|L=Cx_;a4|img3h;?h56vTrn7s=!1X_=P9jdcK|>c;&1pGC&g+*Vb5Ix^Vbj zR_s#VgO!tuuagwx9baEdXbPnAwjZ|?EiX1e+R#f8VO5SfsrOD-Ke^Y{g{kN2+Des6 zRBcu3i;6T1`YloiK`VDvIomUcu83T1

ysuw1rMo89cYU*=lCP*m2@xzt&?rhgKPF{kA$nevz=Az4H|-sri^&TQdHC5YVn9Adz5zA3AQ&7MwQw&{f-0ZEr_ zP+?U*J*_EftO>-s@)Q;N7__)XK1JGWQk`^#)g0+fsdMFBI+60tF{O7#kIiQbxYooXGYm*WkW;@Z-- z-aa)xw(T=He7~YoBwJwPuD+^`xee`B6=`*TOo z4ZO1DvP&0p66$@4nDC)YxJpgr?0P(q;t0@MHNZ49`9I2>bRdoJT4;_ao=>|bEM$+S z4h50DX}PZl;<=-qLu3}^(=b`+o9I)Uwbj`XiDJ|s>VMnEZ_%s`|Jggq29iEked&Gx>M3VJq}ehMRl}z)DkI{LnA6TjJ~={vUifk>VBjrhnEsHj@*Eue-|u zbs@kpy~LT4uKKLTi5k4g2~hb&4?xRO6TU`PaHRzu;&V&`<%cgRMjqb>f~JMt9c+`o z!q0yLg$d8dIu-SC`%h~ICD8<#u{%X{ZLfZ|!?dFpTMoqEEpNufzq_43ABon=b&|Am zj^6;j<3ll~g@6}W2*jV~t``rj5`v&&p)0_o`B+rMyA!Yij7!??WHSBJv8GXSsV$~jK8aZyxV6$zILgg#UDoS1vk{eVT z3Pn8bTafV46gzGDp)(;^+>g>zZg%_1=FW5#4?r#q_=1K@nOiFZ8Dj}8>vaOJMmAJi zC%rW&oygF9*(*-9;TAuAh{7Q*B4Evk(JU8?S8ghcth`ASypP)sAWEE>k?PSzDsw1s zFs2zLdS76LxLD^ zy^W%3v#qAdVERMR3@Sbg8c|4M6hj^Tx!DPf4va1y5Z4T^y_A%(5zYH<0AceJS9Bky zLfs;RAHxb`j{o$V3&Rril6wo{(%%&+y#j%y=B60oA~;DT8-@a?U<4$cYIMx+%m8a< z5H-Fnrd@^T#JKv!FrNu6*L~5(j6GyN3V4{z8tJhvH=6*JtH~| z1b(%wG+|m?3I*Y5$%z&po?^(o>{Ty#(YRZB?^G8skPjH8Kyp8K^e4lCJZOg5&-^Mh z8b+v8tyEouX^K)kBV|_bwzN5NoXZ6*71TqDu#y+qAIQN%Iha^nDtk}6^d7;mONSIy zGsHF^ZU2T~V4^0JLK+0ASkYd|cre^cPRV5PbqIb4zKqhHKR%$L=2CJDf_?_HJ*Fke z>P7Ig87$ibS(%%Px=1OmH@Y`EiTdpY^w4#78p_GZ(X5<)7leO*aX28?a6=JLjrTUw z>;I`Ys8$O#nNR4M{(r&Ki5%OJ1I)en8U6-durVozSzvE`!;eF*yItZbzD+`o{znI< z+wCe`ni4=)1T?y#Q)*;-Dj!+Oo*dMA;0p?|)gqwKSdy3L1?O;nXOebuvl6ahByG z!{W*Y%mfC6i_#nge;2~~A2Ap@velR?`?7gC6~JUdI_R!{vK>_|RsEpwI;Il57GNewc;Qke+C+fF(k|Cp6CSX@OW6luNQ z7b}Ei?xf+PbV@9w`aP-6)fZIb=uxzv&lM|3?4V$8*|ti{@yoeUS7n5heecz3*wCS= zSr+TNbgneFhoFjc+@86xWJH%9FX=}g3!g@Xx4r4d@;S$O-A_i9JIT(D4&Cy^u%pX( ziy_oxJ#+gLgX^rj5l7C+jSeyHmPrUt+bJ+B(U$hNYHQo%&o8n$2d5X*1UFF##)^fL ziDg!~W7*OnIP0Pc{TREgeaj=p^L3Y1=C4z_g_x`}oSRHsfi56O0FAV5rMdILbYJvr z#iip;ojbquz0YzJWYpg7Y&3QiXbxS4XX}N5-cD$yjkCngZk*0?lc{ay#f|8%`rFU4 zEtE!6SXg_S)U_1W5FSR{JJxH% z*}W>{DUsUF5rt@39eow(E|U9WAWY;=acUM{{b+;x_oGQDng3@pvL9M^l*=kl5 z!{9?A%ySsuhzNC!$8We{4^eH4V~2bddY+25@i5c!$W`S*ozY~H7X#8(Q>gC8Brkh} z;Jg%whjA(un{^zDp=`2B~SxB6S_ig$GP8?&YK-zPqN6Msp3i zDWcu@uUPx>KSrLl7gqlNP!;19e&Br${&EP36S4ui0*<|V_;Cz3afkY(C0YGhCTDdWE zg+-ZW^CR)VNPkD)Z-sID`0{8a2J}^UkHy@qxcPGZo(x8Txx7t4Z4A zLYZxZ*aE1s1MyqmK*R7um`k3+(hs+yQzpzBaICOcQC|+uvFs||tox!%yDqVV zb6ATJ1p@KeHVNYKwOhiA|0xel1_MI&pfr%dcRrPN4_jedU^yWKV^ATy3kmg)Y~=lG zSQ6cbeL3M@yDSQ->_~i5^CF>T%~G15wRN-aMPOD^wwXB~KvxRBC+`R9f;U2XkGpNR z3_dO*KcMBi-1GHWg~>7BpK%_#g;ix&buIlD$oHSmb*B}oWP2$DGy-#z(rw09zu$YC zlpWkeeSbKk;;O#Vs`q|jCV6d1=uJEGBa1XGXt;zzO;Fpah!FP3^$+4Ll5|q~St}y_ zf5Qgbl}li;N3o%}tJGul45t3zJpk&g%!@j|lUo!N#NkPnm7UKzRC7s8$Bi0 z*!Rg*8Gsv)(pn3VOH#6i4VSGEh(Q#2j0XbHT)1${9gG2^SRkB<9~drx&KSG*vXz1P z-(toc8WD#JFwPwl%>hztqeH9arZb4wC{IMsOiG~r50x@hjKA#WO=|4*Zo->qq)^=Al2_vRf;`hTd-tL zb4R6D{zIb#$1fhz{DCK4>NM1#1g}woRft;Djc*hn5eMx!W7)A09Q|+E z1L?CN#aJNlUF1R_jZEAgLyM4!*wN&Q_-^v1DVkh!^9>nx;9r*U@@?FvcS-BR+(BBP z7sLcr?=_Hm+)z)=x>6;+E%mvsMChMpn+By^rBWxb=|j`9 zL5JL$`^^ao4!0Qi-6V`KiK2u*EF?J^Mq%V6sz*hbcB*9rU>wq(1!}v3b_OX5ji_jV zsvt~z_t2<&EHrygjb!hIqYtz-(86{{>(%1-r73q_faD^h3!)4uC@8Qgf=MG2zDk$l zUh0Vnk4Yy6MoTBb8tmX}hhP1y5Vicwg@m6;6Cp%vQlv=jq5%g%1ffhOJ!JzYpY+KS zft^+j84SO}gd;R4TEde?>nk}F!t4X49d!U}F`7ql#|?-@4{IFT&PJ6B%075D-oP>J{jP5lNB1Sq15==tMzCMD01tt>yR+Zw*yzoy8 z5v8CgWK3vjf?Ilb3xYo7T%}im!-K3;L8f)33nJHa?^ z{v#8T#-d`n*z!nG!1`C7JQDIt?r#}uxSG zj=yU%PTSUweN_`X2>^Dh`x%T1iM6j4`o7hqk5^8vd{28TY_cY*0wdh74D-KUA62f4 z<~;Z{Ubk-C2D$bkHV1WiCQFIGD%29zjU}PT~K#%?P>)bEa4)>qfVJrbEg~7#QJ-vmO2=rUMGM2 zeE}qa;$)Ev%F{s4rSd8_{Z4z=lF{wN6=MvW5tON|#&ow#wcX4;{17{KNh>>e;%Sd~ zW@eqW-I&gZZRo+3@$g<)-1Y0R5_Hg=dlS8Ixp$Sc>oQ5Tn1wgDoomMkxSB{YcD3qc zENo0oqB}8SF%iCI${KsPs@K)4G~sQ8Ir@oTvZ}3_glt0LSEthoED&+^E=`0%rn=_cHKRX7OZoa?1WnN!)Un z_7EqT!Xh%{Je}t5n8w4svd*;|_zRh-;i!=`EJ~n05`eGGnMXk`egG&OZIQga|Jr%Tke*V-|1sice@pFw>p&kVCxVlCvbkp+Y?
@Ku1Aj!^ zEpq`?yl;Mz0#}>0Tzt{b%r5!DHx%AqM&%9H1{Yb!xZv~C&~Bg2J4sRzdMtGetF+k z#tzePec2;frujJo>RD+J_T)*!(#h#c$$RK5O$%n|mMd;^W_hdZU!xK_8`^XzLY@v9 zIT{ul>AJT-hsHNiJH-C@>39V1Cr46+LEP~`Au_U1MBxf{%J_d78D(=YP);ZFnFf0U zhT{o`Z^z^Tv`L#MBo-*RLSRoF=?oSPhb7qHM43$E8G?_mM${b*D$=Tvk$n9;1Q$(T8xNl3qygeIca?YPy!uxr}_?H;hQJX`7@(1tbXu3N3`MFeO z|MN0a*6t>5W7FmV1f(hZvjpjcS?n7DB(NlnFQqdRzEUanUY@M^&oHxgd2Qj?_vP>E zrudh~PyF6?7AR;)Q2neUtF@(JHvBHX*JR({&o}#>mj&WE z#EeAiUEv!&fT~xV=u$;d<&G;*`{Lp-$<5fBFsYUD2pm*^%>Im{ex6dcFK~!BHAYBZ zW1Q~3!dDT_a?)b5sf}U~=+F-xel|fO8GTUrq8Ysvm4C{W2Z3X%>{(cuSZW?E#m}Vn(+1PR=AC$1J8SZOzum;o;vlmUtwv68Dl`a=++NXWsFF^^-!RAZdEonC znqmz7mvPeSW@w$LId!7urQ-%BHVxR*xGW-|P=^xXmVU$|F>IEoKE4L+*nUZi-tsqg z;}F9a`TQLdmnyypb}DYGw2F)%tOU^0_+Fyq)+p_P zj$ysTg2rlR9Tn4~OvbkPNQgEc32_o?<~1u(vgIC4yPp%w1CSw+zHBm0p(S4ep@O_g zzqwR$1?J#w{B>;O?jwD2-VuYwn2h;RP2}ps@w!WU=3OxGT=hBFleq&?b{1pTs{4t-GZ={Se#_xk-Hj3uMNSZvU`w0s|cax-KCd zf;0=9j97ER04j-w%(J{)#SkPKR+K^X5)N4ZiHyK^kx|`FP0d(s>|cF5f}0jQtRbRA z!LQZ0UehJJPQ6s}@2b{uCLxwOwn9VUbR42G<($4J$!e7gDgtxU2DnzJhju>!KJTukem zQi65`;;g~DLHye$6Qg8yVbWcZIKs=D<^Q&uk?}O<1TFD>OLQ-aZ>J5xfr&3#4XcPi zXO82C$H2p6Y`ej<_|QzO(oP@C$+W2V?=Z%M$0FSB)iOz(8{QAEWwfBm-DUp}H@-|4|F-0dTc0J$v+xXm7tgsMnHl}!TNKoh*<9&CuCkuLWP(85gM za4Hv}EFlyCR%rS}+%y7;-p0}l1HoG{xqMU?NgqnAHp8NPI1hZt(^guKHz$@$Qa)9d zNQ1=!kLfyC7_-s8!2e^}-7_+Q#+;i=DVyE7vwoYX1L_BBq6u+dlZH%aP&ClV-8Dt@ zt3P&qD3lI9MmfC;u)gsX!Tl^+T-Tt;vtB7Kj8% zg)GlaD>9~+_>-0ik)2BkE;gOV$s?3ijQAN54wY8v-m?!NvNNt9$YV#SNoa zPHe7}?cHNR4)??Z0~Dp^>q+y=xy*w|QsiGV9A$lBDc~mw3zDx5`V&B#PtY^F&_(r; zu<`XmbAD@dsh!iO(0w@*p9op7DughBSi?<|I#{K?j1XC}ll){-u}HPWAe2rW z?%)uwVfm?;Z}NfuuYkduKI6uk$Ilz5r*YJL#2DjX)Xvq52M9Q&K8Y{~t(oR^-agoV z@*%h|1;aX{>|sGxH$X5o92bmmuqXDmErr|ES;+j-ciCX)9~xa?!Z&}=;&qf{(rmb) z(9k%RTcOp5xq4c#V-EQxq!EN+qEMT6ch{1B;JZ@_Tb*)I3VTYKcrye)G~{`+jEbMN zrlA~+T@J8S*kR?V-9PTwowjYNA86BgKK6v}E{-kV9;R6Tt_JF?MP{_8bFHQR#j^VS z_oei4_Q;zT{-7m&k*(kTc#Ap^-%Ypos+MkgK9cSE|6RbfL)D7IS_pA{$H|9ETesx% z|2TmH1yAaERN3b%@px1F0`)1|*qljHm^Y#4*dnlq7h+$js5ckxUkT@r3l<|g_kPeG z$|`&AS2B(4c|2lf8$5l=4yWs9KAwLy9H)oH-SYpphGR+Abv(Go(3^qG5TsO4N@+W(LSltT3{fLxoBhgipNF zPbM;9Ki#Ix6KbBPL$&v=zZ{kJgm%1(IQFov{^G-%#P5arES{tAZ;z^3=b=x;I}3Y< zYLs&iPut9t7jOvOw&N!hYCqdF-wI%oJ;(pAiYxz6aiMsEV&NjkKnrP%@ow@lOmU)A z3MDP`%Snb1Lv*a@AvyEEX6Sbvr$_$Ae0paYWxld%LELoKzgc z_e~fOII$lM6rdeL#<5ev*`kfQf{nRCiFUIuy+2C6{c`5TZek5e@>``jF;^-ZD5;#? zXDD8Sh+ws@h$+TaK1UQ`$-!AA-xFD_ZR2m!8r$&hd@RILl{a(yo%B$((dm1A?3a-@ znyl^TbqbHazjyuZWFPP6^|j8_LBr54aoZGa-1qRz=e}w4{zEkt@Ww#L+Eu>fH_R#` ziG_qjPb9_F^d(95R_#I@gPEK4183HB!@(z(<;U$~yvpY{1gvri>iTru5M=IG8-$&P zhL`J!!<&1Kw};>MUF{yLnB+CAI`p;83G=n)-znoQ|1R;NVAZ|=kqlR!Dh3|d!H>x3 zwe>iy22?dWtKW|qdE@iGVC`c#7(x|9-^MIf;j3-jskAvaPYzxi{Kz`drqTZ~XWF=< zkG(@f&)+_x@a#gjCsi6@eL(%@u7wKhH{SX$Fj#Vm8|K-;O9Rwe^EN0k+)wmmzp5p4&@)i3OyCMlvPCZd zCyrVUTM}CmO`F);62yHrK?gssK_?~PQl+X_(o(1fB5^=g11mo8>JZ`(V#r+yVq+V~ z?V-*PDGCH(VHRiH&r|r+VWOy^Xw#_S!0}W?@gqc`P)Q(vJP9P(@Om^EAi=GY}XEwCnj21k{Kpc~=}{{bGjEFtOf+c#bJL^X_R+%n{NP z?Sq-;1WRAwJemtgOm1|^3Nv6^IR7d}Rmg=tccRt$&5)}FE^nf#aBDZn!Q~0(=Rz#> zSPyy&NeT@!qdG~f&s^^DeCK*Q5vi%k7^42`7xsn`)WM`qZh4(Ak+s{XB)$u=MXQhx z%@Fa1{U#9jwh`pv=jT{H@WVV5Ro8(45PV3<(g`L!{|9ZG7e|UuLXcc8CM^n2cG3)Z zO9^7`=jW*U0*A{a>`YL+FMW7lZcbJ>m`zT$o1ZcKPS`W=gq_dgxn&Z;7d6pGLxcOTh)K$5!A>aDeVbv@zagah%sV#0u9{4gt= zxctHu+x&217@QCzb3POh3}6G@B^2Fr4J>QLB)il~!)@A`*$vI9uYO+U>GP7vqFdz- z0a4TOT34J}J*;h(2Ar=ZTxM`VXE8O!)D|v(Ns+|FN=9dk$El9??gfX@5p697CBniN za7STktL(}~uv(^?z&fv!a*buw#rl0JwD`4iqmycHjb*}V3L^^f@C&!Q zR08arR#KZawzFfAg?%uM&rWrE88Wy2hZDm07)r8pZaLo}rAWG9qIuXj5cZ08Ylfa)1LQ@Ur%9ZaqUqUVeGP|02v)W?H zmMUs1_7FruK}xPIVNfi{BFHUE@mA1TJ9Z!-kBb!e@aAsVT5%SM_r!3vD(P?-l%^;+ z?hg`J25LELZajIdvOW3O&>RxLSeMmq*Bw^Gy4R#K>eU@_Ah2!&oiuWoWQSze>it|@ zCog6BerYn)DAoBwzcGtTc~5c)-eP9{<%b9T;m{a%`xuI?NPl`>vxROZAEJsPvc8sM>t2u`|$(ITXzL{9FDdCVI;o8eA z$BNAZ9dT;A%1B`GD)%>A_VOTh3ItWbTXs%tN9wP8POk-$9E6uA44TrsNJpwK)DJ-^0YeOZF1z54#eCaiux>Febu$eEn zJo4+Gskfj5KITqKTB4%TXev3vVdoQx8aw=YKruLVV!ixQ!zZJVip;3YK{ zesEGl@8Cj%G1Usl-of%|?LZsEVzRPC#YALXDN7@vVFEZ07TUux&y!dNxz>SfTr{VS zauaU|;q3>wqcUu37k`6x=)zfpE9ua{Ujrs|DD0QCnD59%9d*-vd+~Vug}LGql7 z|Bhba!EdCZ#|YU{x;^*$3G<)5y;79F{kc9y@T^j`5f2jD4kNRM4gV>Ol&r-t`O&zg;!hBJ<%*Dfp9mWxsiXJGH>30Lj9o8Nc2VpnuCeX3^nG zUUk@=uVcGBXWw)VrmvZ1&&nnuB<*CKk!iXhYaqx@~+~+eb$w=$?2dv6UT!`nh75GYdESOtYa#1V3#0U%eLp=aSCDuVgfb?2!9Ra7!KTr|>mYIzT- z%}dMAUgtemsm_(xCyC0u4oJ<1i=LCj$3F8sxH%6<^G{O zE{rZa(zrej;>#w`???kjs|~|qK^FGi64T7T>tt2*DFYIPf5pu}GFf0|%SlAu22dg4 z#*vbRdN=RQ%MRvG0W@;?mj$?{vbgb4)Q^rKKC5Y=1;Yu$hz|e4xnkuJu0R=#4FCqo zu#8ewdOCs5SHSrE@<--`4%B6u(7wm*Bz(Zz=^OD|YkMbcdCFT?(~aG~=#IBhUl0?f zw&LzLf*hQtIbc-Q^A?djOWz(5?_ve8r~-16s- z&~Wq)eh`n~L<3(t`FTlLvJA&o2xKY@6beTAXFHqyn#ZPMFZY%(7`0E+Fi0ceaP>n_ znH{+>FjC$Tp4!~rA;awWcyIBSs=2v&-KAHZ^V60GYAJMBB6{j}!A!^F>D~F&6ALzW z-C5)103+kY3K=8gn-1^Vz~0(3CN_3>reDJFW2BDG-O!<5MC6!W+>Q+lc8VJ&I(ACd z$@X?Fl~9MiNm2CR@8(2iU77-+kfUAYE2y+N$>}ggwjI^PzE~EO9B!4a4544&HL;%) zzvRSbdNxw7ufb%-00HVNiqWyC+QZ-W7&PSIt1+Q9zZRZPF|cw=mnWnkIYldDQ+u{6;RL|F=eQcv>56JftCX|<0*RMC zFC>?cn3S?jEtu0)g zJ$zS|kEl0`{gGNe5*94!j}C3JAzT7eOvwoRn9Oc4Lq&t?RI?G!Ia5+`92OFhMY4c* zn2TN{NBlaJ`=f-MsiuhQ zuOJ0(6*i$Cl*mqjKr}Qmqv0RgzR|!5+`=rSwEP*1TZ%lV&46?`Wyk0rKTiTU@f@{k Vakhr+dtRdIxW9GEfufa@O&qdu*s%eOs$SDbBC8Vf0Aa#u)cwVr{H;*LG1$zNztSLIm@BvAL z5*b2;)1V%wHJ@7EF YEcZ5u+*KW&{wKrFzn1{~reh5N0KiCXJpcdz delta 744 zcmV;=#TTz_TsKv{>|Avr3p=@2?H)qK zIW=wNPlFBPKV-l;8sjK}$)Nya>A(bGfXE9X8=~QQ^5C``#=GCL(}1gbUip99eneWW z=2Z+maaJs8u`lKOs=XOsZF92Ka(1-tqkJ#L)G{lt;7S+^c*&+Pog#v{lwyh;f1Z}p zzGp5@yJ3q;|iClTr^{Z|bJ!*+~uyeVcy+xkb8Jc=h+@ zxWu2A>Cpx5T>@U(5->y#R(O8_0!=BMl`5C(#H(w*KaMw>vx2kyl&la~o@~ip$7LZ{ ze(XSHWMO#0@u=>99)((v@5fTPcP(&)Xdzd{vGbmF)9;A1na@8Lei`bI&G=nh=N2Ce z#>Ht`hmHGqZ>Wo&!Ist)?TgiWSA`xksg^!NdKZr*(x^jPyedP<*cDoKJcr1MqABRGc&7*j;TByIwG0zF@0#$nPb z7lj6?WJZr>CPPT)tIA1}Gf-%({EnVy&|}SO(BnV-qh|BmP^(xoGmeXfF(yG<-Q7tn z<)cd+l0`3tLyT5Eiykxu&(n$!;>?JX`$y!@P{Tww<;X|olr!itu_KGKAP9_tV0!3b aU|H^M5V@;5KK)Mypnn0M>kI*D4FCX&;%>G8 diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json index 1434aae60c967..ac35ef3dcab99 100644 --- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json +++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json @@ -2,7 +2,7 @@ "type": "doc", "value": { "id": "3KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579881969541, "agent": { @@ -57,7 +57,7 @@ "type": "doc", "value": { "id": "3aVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579881969541, "agent": { @@ -111,7 +111,7 @@ "type": "doc", "value": { "id": "3qVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579881969541, "agent": { @@ -163,7 +163,7 @@ "type": "doc", "value": { "id": "36VN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579878369541, "agent": { @@ -218,7 +218,7 @@ "type": "doc", "value": { "id": "4KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579878369541, "agent": { @@ -271,7 +271,7 @@ "type": "doc", "value": { "id": "4aVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579878369541, "agent": { @@ -324,7 +324,7 @@ "type": "doc", "value": { "id": "4qVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579874769541, "agent": { @@ -378,7 +378,7 @@ "type": "doc", "value": { "id": "46VN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579874769541, "agent": { @@ -431,7 +431,7 @@ "type": "doc", "value": { "id": "5KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint.metadata-default-1", + "index": "metrics-endpoint.metadata-default", "source": { "@timestamp": 1579874769541, "agent": { diff --git a/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz b/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz index d9fcf03f43f37d02e93a8c20bf8a1ba3d33ee9ce..1f492487d317b7522608650c2c2b8ca9e9278280 100644 GIT binary patch delta 1302 zcmV+x1?l>)3abhSABzYGbI0tF2Pl7{EsDaRNXp?x76nO;H@nDpFR7RHjO{>P^WwOpTL3&vba?ZPdMDGmhbY1{K={M`sw~ltefj!|9SrX^OJ9f zzkUAlkee%uh_bKBXveaIlN7Ru#)3!LG8Q2x&ycbJ@58LJPsKhV#~x(MFByN|u{6WG zShnhx>mp=2nr)i)ZDoSDEXo=_H5t}5Ol-@A*z^fR@_*gZg-Us=K zh$!IOeZn^7$Wn$A^01NZ5zE~Mynrf+G?l|vLu@$$B6hnJ)L|(|_k>8}U>}Chjrpo0 z$@0kdifvI^E;=?ZU8e^0BTFI{o{jc!eWtR(@k*`_K@(5 zolARM6biNA%mZ8(l<$9J|8kjNdU;w^R&klSpU5SJ3&wFiO*P>bjI1{kpWZ^*fkU+EXA zJ4?^1R|q-O_#5c5;f>xIN5(pE_KaALUYoC-oYx*=Z%Thoe+WNd$Rm%u;UTA)+=O=1 zR7Ag;PnP}YOTV&fzCtIR{)={_{$|!OXsA;j!n#J72R)q_5ZRs&UBhJ1V46b%(?P!N zbloGo%lF^Ec=iC+YUVr8N4&lcl~+nzgd%cMvs`W(RQ8TSo^LoF!xqFk#Sj^m2{AD+ zbR0|(G8}(uQM)PRJ@P2`D@3kMz18-Z9z;5*9HPMVJy$7*o=qWA!W%XX8ycoY9mh7Q zBa>4_yQ%)Z+`Qb}s95JJr(s7q{kHvdP36?K%vm{|mD5I~Svj4R(^)y426k3XXXSKO zPTvYUE2q;j&C2PloX*PWRGhPNIxD9GhMfaoz+!(=0c<)!bYO2dSO2>9Ag2>H>jg0~ zw1FhNVQ?3zwi8R3&DYx*YHjl=b+*bsVt6XKqmPYA>*u1~rXO%z8Pzn`_YCSn9}@}@ zWd?LD+lP)H=-5PtQ0LD*=OaK4? delta 1306 zcmV+#1?Bpy3a<(WABzYGXDsTG2Pl6xcW#v2Lz^{rma%k59fG z{`T?nLvF4tBFerfqaDia#)4r`t@Rmhc!>4A$x`v5uxe%K^fk^%jJ%li{UF!K%ClXx+P>6%Gm^G@ZpA=@E+I}cPTFq$G1Q|VKJ~F~ zyHNM23sK+^=rYfNj$s&%<}rWQc7sLj_R{NbT_(NVVKD>U9&sFpoY79R`am){sPWIYvb|zkl$MK*%}dv*0sY95h=nH+?=cT0N+ynApmcqeD3E^A_7Z=hP;9q6+MY!E zqmhpWsR~-amko$BVrb9*ppn~L+j@i!ZZ_cEa_AfprU|03P@Nil5DyuZo(cC zp0RUjkBdT~7Myv2>wn3MpFEH|{v`!a#Q^KS)?g>-seSQsxFYEJMCvSi1{mbI5)lvq$R1d|8R5L~ zU9oe#p9ogYiM*_oQ@ts=`oWR&+@49=JcvPzlT>22;HG3MPl|st=Y5@@G?b+*$v1U% z*3_l=Gl&F`*H=!f^QUN+XMm&&W%efmPng=1y4Rp{QrlYgDhyb6E=AL)xuCtMR{NNR z|MmEaYQK!HfmgU+53jszoy!++7E-bk za#o$r<*cN!d{GTurxz{!kyH4U>qHTOs6o@s@#XflifDDGy;?Bg})IP7H`_&xfvIGH5W(p@Hcj z-*&q05#Htd@1Hz-0Bbe#9q1!oUx&&or7c1cIjLDLHw`L#Ml7YFp;4ozB{6Bhsv$&f4j$olXNgYp1hz zI%}tIg`Kt2>6m8ibkuDGE=h{Cbq`{y(!fF QkJ<|8e~b=XHCHMC0K|re2LJ#7 From 1cdeec07b2c517c16ab12d80d716c17dd7158688 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 23 Jun 2020 18:51:20 +0200 Subject: [PATCH 11/36] completes top-level navigation tests (#69694) --- .../cypress/integration/navigation.spec.ts | 31 +++++++++++++++---- .../cypress/screens/security_header.ts | 4 +++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts index 67b72982f44e0..28ae42f8c0974 100644 --- a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts @@ -3,7 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ALERTS, HOSTS, NETWORK, OVERVIEW, TIMELINES } from '../screens/security_header'; +import { + ALERTS, + CASES, + HOSTS, + MANAGEMENT, + NETWORK, + OVERVIEW, + TIMELINES, +} from '../screens/security_header'; import { loginAndWaitForPage } from '../tasks/login'; import { navigateFromHeaderTo } from '../tasks/security_header'; @@ -14,11 +22,17 @@ describe('top-level navigation common to all pages in the Security app', () => { before(() => { loginAndWaitForPage(TIMELINES_PAGE); }); + it('navigates to the Overview page', () => { navigateFromHeaderTo(OVERVIEW); cy.url().should('include', '/security/overview'); }); + it('navigates to the Alerts page', () => { + navigateFromHeaderTo(ALERTS); + cy.url().should('include', '/security/alerts'); + }); + it('navigates to the Hosts page', () => { navigateFromHeaderTo(HOSTS); cy.url().should('include', '/security/hosts'); @@ -29,13 +43,18 @@ describe('top-level navigation common to all pages in the Security app', () => { cy.url().should('include', '/security/network'); }); - it('navigates to the Alerts page', () => { - navigateFromHeaderTo(ALERTS); - cy.url().should('include', '/security/alerts'); - }); - it('navigates to the Timelines page', () => { navigateFromHeaderTo(TIMELINES); cy.url().should('include', '/security/timelines'); }); + + it('navigates to the Cases page', () => { + navigateFromHeaderTo(CASES); + cy.url().should('include', '/security/cases'); + }); + + it('navigates to the Management page', () => { + navigateFromHeaderTo(MANAGEMENT); + cy.url().should('include', '/security/management'); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/security_header.ts b/x-pack/plugins/security_solution/cypress/screens/security_header.ts index 89deeee0426d0..17d8aed1c2d21 100644 --- a/x-pack/plugins/security_solution/cypress/screens/security_header.ts +++ b/x-pack/plugins/security_solution/cypress/screens/security_header.ts @@ -8,10 +8,14 @@ export const ALERTS = '[data-test-subj="navigation-alerts"]'; export const BREADCRUMBS = '[data-test-subj="breadcrumbs"] a'; +export const CASES = '[data-test-subj="navigation-case"]'; + export const HOSTS = '[data-test-subj="navigation-hosts"]'; export const KQL_INPUT = '[data-test-subj="queryInput"]'; +export const MANAGEMENT = '[data-test-subj="navigation-management"]'; + export const NETWORK = '[data-test-subj="navigation-network"]'; export const OVERVIEW = '[data-test-subj="navigation-overview"]'; From 1ad18794efe546e58412c62fdc8dd7be7465a070 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Tue, 23 Jun 2020 13:32:03 -0400 Subject: [PATCH 12/36] [CI] Record Github commit statuses outside of PRs (#69432) --- .ci/pipeline-library/build.gradle | 1 + .../src/test/KibanaBasePipelineTest.groovy | 7 +- .../src/test/buildState.groovy | 48 +++++++++++ .../src/test/githubCommitStatus.groovy | 85 +++++++++++++++++++ Jenkinsfile | 2 +- vars/buildState.groovy | 30 +++++++ vars/githubCommitStatus.groovy | 42 +++++++++ vars/kibanaPipeline.groovy | 13 ++- vars/workers.groovy | 6 ++ 9 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 .ci/pipeline-library/src/test/buildState.groovy create mode 100644 .ci/pipeline-library/src/test/githubCommitStatus.groovy create mode 100644 vars/buildState.groovy create mode 100644 vars/githubCommitStatus.groovy diff --git a/.ci/pipeline-library/build.gradle b/.ci/pipeline-library/build.gradle index 2753750871ad5..ac5e7a4ed034a 100644 --- a/.ci/pipeline-library/build.gradle +++ b/.ci/pipeline-library/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation 'org.jenkins-ci.plugins.workflow:workflow-step-api:2.19@jar' testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.4' testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:2.+' testImplementation 'org.assertj:assertj-core:3.15+' // Temporary https://github.com/jenkinsci/JenkinsPipelineUnit/issues/209 } diff --git a/.ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy b/.ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy index 23282089ab76c..086484f2385b0 100644 --- a/.ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy +++ b/.ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy @@ -19,9 +19,9 @@ class KibanaBasePipelineTest extends BasePipelineTest { env.BUILD_DISPLAY_NAME = "#${env.BUILD_ID}" env.JENKINS_URL = 'http://jenkins.localhost:8080' - env.BUILD_URL = "${env.JENKINS_URL}/job/elastic+kibana+${env.BRANCH_NAME}/${env.BUILD_ID}/" + env.BUILD_URL = "${env.JENKINS_URL}/job/elastic+kibana+${env.BRANCH_NAME}/${env.BUILD_ID}/".toString() - env.JOB_BASE_NAME = "elastic / kibana # ${env.BRANCH_NAME}" + env.JOB_BASE_NAME = "elastic / kibana # ${env.BRANCH_NAME}".toString() env.JOB_NAME = env.JOB_BASE_NAME env.WORKSPACE = 'WS' @@ -31,6 +31,9 @@ class KibanaBasePipelineTest extends BasePipelineTest { getBuildStatus: { 'SUCCESS' }, printStacktrace: { ex -> print ex }, ], + githubPr: [ + isPr: { false }, + ], jenkinsApi: [ getFailedSteps: { [] } ], testUtils: [ getFailures: { [] } ], ]) diff --git a/.ci/pipeline-library/src/test/buildState.groovy b/.ci/pipeline-library/src/test/buildState.groovy new file mode 100644 index 0000000000000..b748cce29e7f4 --- /dev/null +++ b/.ci/pipeline-library/src/test/buildState.groovy @@ -0,0 +1,48 @@ +import org.junit.* +import static groovy.test.GroovyAssert.* + +class BuildStateTest extends KibanaBasePipelineTest { + def buildState + + @Before + void setUp() { + super.setUp() + + buildState = loadScript("vars/buildState.groovy") + } + + @Test + void 'get() returns existing data'() { + buildState.add('test', 1) + def actual = buildState.get('test') + assertEquals(1, actual) + } + + @Test + void 'get() returns null for missing data'() { + def actual = buildState.get('missing_key') + assertEquals(null, actual) + } + + @Test + void 'add() does not overwrite existing keys'() { + assertTrue(buildState.add('test', 1)) + assertFalse(buildState.add('test', 2)) + + def actual = buildState.get('test') + + assertEquals(1, actual) + } + + @Test + void 'set() overwrites existing keys'() { + assertFalse(buildState.has('test')) + buildState.set('test', 1) + assertTrue(buildState.has('test')) + buildState.set('test', 2) + + def actual = buildState.get('test') + + assertEquals(2, actual) + } +} diff --git a/.ci/pipeline-library/src/test/githubCommitStatus.groovy b/.ci/pipeline-library/src/test/githubCommitStatus.groovy new file mode 100644 index 0000000000000..17878624b73cf --- /dev/null +++ b/.ci/pipeline-library/src/test/githubCommitStatus.groovy @@ -0,0 +1,85 @@ +import org.junit.* +import static org.mockito.Mockito.*; + +class GithubCommitStatusTest extends KibanaBasePipelineTest { + def githubCommitStatus + def githubApiMock + def buildStateMock + + def EXPECTED_STATUS_URL = 'repos/elastic/kibana/statuses/COMMIT_HASH' + def EXPECTED_CONTEXT = 'kibana-ci' + def EXPECTED_BUILD_URL = 'http://jenkins.localhost:8080/job/elastic+kibana+master/1/' + + interface BuildState { + Object get(String key) + } + + interface GithubApi { + Object post(String url, Map data) + } + + @Before + void setUp() { + super.setUp() + + buildStateMock = mock(BuildState) + githubApiMock = mock(GithubApi) + + when(buildStateMock.get('checkoutInfo')).thenReturn([ commit: 'COMMIT_HASH', ]) + when(githubApiMock.post(any(), any())).thenReturn(null) + + props([ + buildState: buildStateMock, + githubApi: githubApiMock, + ]) + + githubCommitStatus = loadScript("vars/githubCommitStatus.groovy") + } + + void verifyStatusCreate(String state, String description) { + verify(githubApiMock).post( + EXPECTED_STATUS_URL, + [ + 'state': state, + 'description': description, + 'context': EXPECTED_CONTEXT, + 'target_url': EXPECTED_BUILD_URL, + ] + ) + } + + @Test + void 'onStart() should create a pending status'() { + githubCommitStatus.onStart() + verifyStatusCreate('pending', 'Build started.') + } + + @Test + void 'onFinish() should create a success status'() { + githubCommitStatus.onFinish() + verifyStatusCreate('success', 'Build completed successfully.') + } + + @Test + void 'onFinish() should create an error status for failed builds'() { + mockFailureBuild() + githubCommitStatus.onFinish() + verifyStatusCreate('error', 'Build failed.') + } + + @Test + void 'onStart() should exit early for PRs'() { + prop('githubPr', [ isPr: { true } ]) + + githubCommitStatus.onStart() + verifyZeroInteractions(githubApiMock) + } + + @Test + void 'onFinish() should exit early for PRs'() { + prop('githubPr', [ isPr: { true } ]) + + githubCommitStatus.onFinish() + verifyZeroInteractions(githubApiMock) + } +} diff --git a/Jenkinsfile b/Jenkinsfile index db9d218db15ba..7869fa68788bd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,7 +3,7 @@ library 'kibana-pipeline-library' kibanaLibrary.load() -kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true) { +kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true, setCommitStatus: true) { githubPr.withDefaultPrComments { ciStats.trackBuild { catchError { diff --git a/vars/buildState.groovy b/vars/buildState.groovy new file mode 100644 index 0000000000000..365705661350c --- /dev/null +++ b/vars/buildState.groovy @@ -0,0 +1,30 @@ +import groovy.transform.Field + +public static @Field JENKINS_BUILD_STATE = [:] + +def add(key, value) { + if (!buildState.JENKINS_BUILD_STATE.containsKey(key)) { + buildState.JENKINS_BUILD_STATE[key] = value + return true + } + + return false +} + +def set(key, value) { + buildState.JENKINS_BUILD_STATE[key] = value +} + +def get(key) { + return buildState.JENKINS_BUILD_STATE[key] +} + +def has(key) { + return buildState.JENKINS_BUILD_STATE.containsKey(key) +} + +def get() { + return buildState.JENKINS_BUILD_STATE +} + +return this diff --git a/vars/githubCommitStatus.groovy b/vars/githubCommitStatus.groovy new file mode 100644 index 0000000000000..4cd4228d55f03 --- /dev/null +++ b/vars/githubCommitStatus.groovy @@ -0,0 +1,42 @@ +def shouldCreateStatuses() { + return !githubPr.isPr() && buildState.get('checkoutInfo') +} + +def onStart() { + catchError { + if (!shouldCreateStatuses()) { + return + } + + def checkoutInfo = buildState.get('checkoutInfo') + create(checkoutInfo.commit, 'pending', 'Build started.') + } +} + +def onFinish() { + catchError { + if (!shouldCreateStatuses()) { + return + } + + def checkoutInfo = buildState.get('checkoutInfo') + def status = buildUtils.getBuildStatus() + + if (status == 'SUCCESS' || status == 'UNSTABLE') { + create(checkoutInfo.commit, 'success', 'Build completed successfully.') + } else if(status == 'ABORTED') { + create(checkoutInfo.commit, 'error', 'Build aborted or timed out.') + } else { + create(checkoutInfo.commit, 'error', 'Build failed.') + } + } +} + +// state: error|failure|pending|success +def create(sha, state, description, context = 'kibana-ci') { + withGithubCredentials { + return githubApi.post("repos/elastic/kibana/statuses/${sha}", [ state: state, description: description, context: context, target_url: env.BUILD_URL ]) + } +} + +return this diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 4a5c8dff8a230..46a76bbb8d523 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -214,12 +214,15 @@ def runErrorReporter() { } def call(Map params = [:], Closure closure) { - def config = [timeoutMinutes: 135, checkPrChanges: false] + params + def config = [timeoutMinutes: 135, checkPrChanges: false, setCommitStatus: false] + params stage("Kibana Pipeline") { timeout(time: config.timeoutMinutes, unit: 'MINUTES') { timestamps { ansiColor('xterm') { + if (config.setCommitStatus) { + buildState.set('shouldSetCommitStatus', true) + } if (config.checkPrChanges && githubPr.isPr()) { pipelineLibraryTests() @@ -230,7 +233,13 @@ def call(Map params = [:], Closure closure) { return } } - closure() + try { + closure() + } finally { + if (config.setCommitStatus) { + githubCommitStatus.onFinish() + } + } } } } diff --git a/vars/workers.groovy b/vars/workers.groovy index e44df9ef75ad0..8b7e8525a7ce3 100644 --- a/vars/workers.groovy +++ b/vars/workers.groovy @@ -65,6 +65,12 @@ def base(Map params, Closure closure) { dir("kibana") { checkoutInfo = getCheckoutInfo() + + // use `checkoutInfo` as a flag to indicate that we've already reported the pending commit status + if (buildState.get('shouldSetCommitStatus') && !buildState.has('checkoutInfo')) { + buildState.set('checkoutInfo', checkoutInfo) + githubCommitStatus.onStart() + } } ciStats.reportGitInfo( From 41f085adccd54b5d3673777ab829651726e00f4c Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 23 Jun 2020 19:49:41 +0200 Subject: [PATCH 13/36] Document workaround for using HashRouter (#69140) * add section on hashrouter * review comments --- src/core/MIGRATION.md | 285 ++++++++++++++++++++++++++---------------- 1 file changed, 178 insertions(+), 107 deletions(-) diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index ce2d652257e1f..90b1bb4fd5320 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -15,15 +15,15 @@ - [Switch to new platform services](#switch-to-new-platform-services) - [Migrate to the new plugin system](#migrate-to-the-new-plugin-system) - [Browser-side plan of action](#browser-side-plan-of-action) - - [1. Create a plugin definition file](#1-create-a-plugin-definition-file) - - [2. Export all static code and types from `public/index.ts`](#2-export-all-static-code-and-types-from-publicindexts) - - [3. Export your runtime contract](#3-export-your-runtime-contract) - - [4. Move "owned" UI modules into your plugin and expose them from your public contract](#4-move-owned-ui-modules-into-your-plugin-and-expose-them-from-your-public-contract) - - [5. Provide plugin extension points decoupled from angular.js](#5-provide-plugin-extension-points-decoupled-from-angularjs) - - [6. Move all webpack alias imports into uiExport entry files](#6-move-all-webpack-alias-imports-into-uiexport-entry-files) - - [7. Switch to new platform services](#7-switch-to-new-platform-services) - - [8. Migrate to the new plugin system](#8-migrate-to-the-new-plugin-system) - - [Bonus: Tips for complex migration scenarios](#bonus-tips-for-complex-migration-scenarios) + - [1. Create a plugin definition file](#1-create-a-plugin-definition-file) + - [2. Export all static code and types from `public/index.ts`](#2-export-all-static-code-and-types-from-publicindexts) + - [3. Export your runtime contract](#3-export-your-runtime-contract) + - [4. Move "owned" UI modules into your plugin and expose them from your public contract](#4-move-owned-ui-modules-into-your-plugin-and-expose-them-from-your-public-contract) + - [5. Provide plugin extension points decoupled from angular.js](#5-provide-plugin-extension-points-decoupled-from-angularjs) + - [6. Move all webpack alias imports into uiExport entry files](#6-move-all-webpack-alias-imports-into-uiexport-entry-files) + - [7. Switch to new platform services](#7-switch-to-new-platform-services) + - [8. Migrate to the new plugin system](#8-migrate-to-the-new-plugin-system) + - [Bonus: Tips for complex migration scenarios](#bonus-tips-for-complex-migration-scenarios) - [Keep Kibana fast](#keep-kibana-fast) - [Frequently asked questions](#frequently-asked-questions) - [Is migrating a plugin an all-or-nothing thing?](#is-migrating-a-plugin-an-all-or-nothing-thing) @@ -59,6 +59,7 @@ - [On the client side](#on-the-client-side) - [Updates an application navlink at runtime](#updates-an-application-navlink-at-runtime) - [Logging config migration](#logging-config-migration) + - [Use HashRouter in migrated apps](#use-react-hashrouter-in-migrated-apps) Make no mistake, it is going to take a lot of work to move certain plugins to the new platform. Our target is to migrate the entire repo over to the new platform throughout 7.x and to remove the legacy plugin system no later than 8.0, and this is only possible if teams start on the effort now. @@ -100,6 +101,7 @@ src/plugins "ui": true } ``` + More details about[manifest file format](/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md) Note that `package.json` files are irrelevant to and ignored by the new platform. @@ -121,8 +123,7 @@ export function plugin(initializerContext: PluginInitializerContext) { import { PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; export class Plugin { - constructor(initializerContext: PluginInitializerContext) { - } + constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup) { // called when plugin is setting up @@ -155,8 +156,7 @@ export function plugin(initializerContext: PluginInitializerContext) { import { PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; export class Plugin { - constructor(initializerContext: PluginInitializerContext) { - } + constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup) { // called when plugin is setting up during Kibana's startup sequence @@ -234,7 +234,7 @@ export class Plugin { return { getFoo() { return 'foo'; - } + }, }; } @@ -242,8 +242,8 @@ export class Plugin { return { getBar() { return 'bar'; - } - } + }, + }; } } ``` @@ -255,9 +255,7 @@ Unlike core, capabilities exposed by plugins are _not_ automatically injected in ```json { "id": "demo", - "requiredPlugins": [ - "foobar" - ], + "requiredPlugins": ["foobar"], "server": true, "ui": true } @@ -376,15 +374,15 @@ export default (kibana) => { method: 'POST', async handler(request) { search(server, request); // target acquired - } + }, }); server.expose('getDemoBar', () => { return `Demo ${server.plugins.foo.getBar()}`; }); - } + }, }); -} +}; ``` This example legacy plugin uses hapi's `server` object directly inside of its `init` function, which is something we can address in a later step. What we need to address in this step is when we pass the raw `server` and `request` objects into our custom `search` function. @@ -397,11 +395,10 @@ Instead, we identify which functionality we actually need from those objects and import { ElasticsearchPlugin, Request } from '../elasticsearch'; export interface ServerFacade { plugins: { - elasticsearch: ElasticsearchPlugin - } -} -export interface RequestFacade extends Request { + elasticsearch: ElasticsearchPlugin; + }; } +export interface RequestFacade extends Request {} // likely imported from another file function search(server: ServerFacade, request: RequestFacade) { @@ -416,27 +413,27 @@ export default (kibana) => { init(server) { const serverFacade: ServerFacade = { plugins: { - elasticsearch: server.plugins.elasticsearch - } - } + elasticsearch: server.plugins.elasticsearch, + }, + }; server.route({ path: '/api/demo_plugin/search', method: 'POST', async handler(request) { const requestFacade: RequestFacade = { - headers: request.headers + headers: request.headers, }; search(serverFacade, requestFacade); - } + }, }); server.expose('getDemoBar', () => { return `Demo ${server.plugins.foo.getBar()}`; }); - } + }, }); -} +}; ``` This change might seem trivial, but it's important for two reasons. @@ -459,9 +456,9 @@ export default (kibana) => { init(server) { const serverFacade: ServerFacade = { plugins: { - elasticsearch: server.plugins.elasticsearch - } - } + elasticsearch: server.plugins.elasticsearch, + }, + }; // HTTP functionality from legacy server.route({ @@ -469,19 +466,19 @@ export default (kibana) => { method: 'POST', async handler(request) { const requestFacade: RequestFacade = { - headers: request.headers + headers: request.headers, }; search(serverFacade, requestFacade); - } + }, }); // Exposing functionality for other plugins server.expose('getDemoBar', () => { return `Demo ${server.plugins.foo.getBar()}`; // Accessing functionality from another plugin }); - } + }, }); -} +}; ``` We now move this logic into a new plugin definition, which is based off of the conventions used in real new platform plugins. While the legacy plugin definition is in the root of the plugin, this new plugin definition will be under the plugin's `server/` directory since it is only the server-side plugin definition. @@ -492,18 +489,18 @@ import { CoreSetup, Plugin } from 'src/core/server'; import { ElasticsearchPlugin } from '../elasticsearch'; interface FooSetup { - getBar(): string + getBar(): string; } // We inject the miminal legacy dependencies into our plugin including dependencies on other legacy // plugins. Take care to only expose the legacy functionality you need e.g. don't inject the whole // `Legacy.Server` if you only depend on `Legacy.Server['route']`. interface LegacySetup { - route: Legacy.Server['route'] + route: Legacy.Server['route']; plugins: { - elasticsearch: ElasticsearchPlugin, // note: Elasticsearch is in CoreSetup in NP, rather than a plugin - foo: FooSetup - } + elasticsearch: ElasticsearchPlugin; // note: Elasticsearch is in CoreSetup in NP, rather than a plugin + foo: FooSetup; + }; } // Define the public API's for our plugins setup and start lifecycle @@ -524,26 +521,26 @@ export class DemoPlugin implements Plugin { route: server.route, plugins: { elasticsearch: server.plugins.elasticsearch, - foo: server.plugins.foo - } + foo: server.plugins.foo, + }, }; const demoSetup = new Plugin().setup(coreSetup, pluginsSetup, __LEGACY); // continue to expose functionality to legacy plugins server.expose('getDemoBar', demoSetup.getDemoBar); - } + }, }); -} +}; ``` > Note: An equally valid approach is to extend `CoreSetup` with a `__legacy` @@ -728,8 +725,8 @@ export interface DemoStartDeps { * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces. * @public */ -export type DemoSetup = {} -export type DemoStart = {} +export type DemoSetup = {}; +export type DemoStart = {}; /** @internal */ export class DemoPlugin implements Plugin { @@ -762,15 +759,10 @@ import { DemoSetup, DemoStart } from './plugin'; const myPureFn = (x: number): number => x + 1; const MyReactComponent = (props) => { return

Hello, {props.name}

; -} +}; // These are your public types & static code -export { - myPureFn, - MyReactComponent, - DemoSetup, - DemoStart, -} +export { myPureFn, MyReactComponent, DemoSetup, DemoStart }; ``` While you're at it, you can also add your plugin initializer to this file: @@ -790,15 +782,10 @@ export const plugin: PluginInitializer x + 1; const MyReactComponent = (props) => { return

Hello, {props.name}

; -} +}; /** @public */ -export { - myPureFn, - MyReactComponent, - DemoSetup, - DemoStart, -} +export { myPureFn, MyReactComponent, DemoSetup, DemoStart }; ``` Great! So you have your plugin definition, and you've moved all of your static exports to the top level of your plugin... now let's move on to the runtime contract your plugin will be exposing. @@ -819,12 +806,12 @@ import { setup as fooSetup, start as fooStart } from '../../foo/public/legacy'; const pluginInstance = plugin({} as PluginInitializerContext); const __LEGACYSetup = { - bar: {}, // shim for a core service that hasn't migrated yet - foo: fooSetup, // dependency on a legacy plugin + bar: {}, // shim for a core service that hasn't migrated yet + foo: fooSetup, // dependency on a legacy plugin }; const __LEGACYStart = { - bar: {}, // shim for a core service that hasn't migrated yet - foo: fooStart, // dependency on a legacy plugin + bar: {}, // shim for a core service that hasn't migrated yet + foo: fooStart, // dependency on a legacy plugin }; export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins, __LEGACYSetup); @@ -935,6 +922,7 @@ For a few plugins, some of these steps (such as angular removal) could be a mont One convention that is useful for this is creating a dedicated `public/np_ready` directory to house the code that is ready to migrate, and gradually move more and more code into it until the rest of your plugin is essentially empty. At that point, you'll be able to copy your `index.ts`, `plugin.ts`, and the contents of `./np_ready` over into your plugin in the new platform, leaving your legacy shim behind. This carries the added benefit of providing a way for us to introduce helpful tooling in the future, such as [custom eslint rules](https://github.com/elastic/kibana/pull/40537), which could be run against that specific directory to ensure your code is ready to migrate. ## Keep Kibana fast + **tl;dr**: Load as much code lazily as possible. Everyone loves snappy applications with responsive UI and hates spinners. Users deserve the best user experiences regardless of whether they run Kibana locally or in the cloud, regardless of their hardware & environment. There are 2 main aspects of the perceived speed of an application: loading time and responsiveness to user actions. @@ -945,16 +933,16 @@ Always prefer to require UI root components lazily when possible (such as in mou ```typescript import { Plugin, CoreSetup, AppMountParameters } from 'src/core/public'; export class MyPlugin implements Plugin { - setup(core: CoreSetup, plugins: SetupDeps){ - core.application.register({ - id: 'app', - title: 'My app', - async mount(params: AppMountParameters) { + setup(core: CoreSetup, plugins: SetupDeps) { + core.application.register({ + id: 'app', + title: 'My app', + async mount(params: AppMountParameters) { const { mountApp } = await import('./app/mount_app'); return mountApp(await core.getStartServices(), params); - }, - }); - plugins.management.sections.getSection('another').registerApp({ + }, + }); + plugins.management.sections.getSection('another').registerApp({ id: 'app', title: 'My app', order: 1, @@ -962,23 +950,27 @@ export class MyPlugin implements Plugin { const { mountManagementSection } = await import('./app/mount_management_section'); return mountManagementSection(coreSetup, params); }, - }) - return { - doSomething(){} - } - } + }); + return { + doSomething() {}, + }; + } } ``` #### How to understand how big the bundle size of my plugin is? + New platform plugins are distributed as a pre-built with `@kbn/optimizer` package artifacts. It allows us to get rid of the shipping of `optimizer` in the distributable version of Kibana. Every NP plugin artifact contains all plugin dependencies required to run the plugin, except some stateful dependencies shared across plugin bundles via `@kbn/ui-shared-deps`. It means that NP plugin artifacts tend to have a bigger size than the legacy platform version. To understand the current size of your plugin artifact, run `@kbn/optimizer` as + ```bash node scripts/build_kibana_platform_plugins.js --dist --no-examples ``` + and check the output in the `target` sub-folder of your plugin folder + ```bash ls -lh plugins/my_plugin/target/public/ # output @@ -987,13 +979,17 @@ ls -lh plugins/my_plugin/target/public/ # eagerly loaded chunk ... 50K my_plugin.plugin.js ``` + you might see at least one js bundle - `my_plugin.plugin.js`. This is the only artifact loaded by the platform during bootstrap in the browser. The rule of thumb is to keep its size as small as possible. Other lazily loaded parts of your plugin present in the same folder as separate chunks under `{number}.plugin.js` names. If you want to investigate what your plugin bundle consists of you need to run `@kbn/optimizer` with `--profile` flag to get generated [webpack stats file](https://webpack.js.org/api/stats/). + ```bash node scripts/build_kibana_platform_plugins.js --dist --no-examples --profile ``` + Many OSS tools are allowing you to analyze generated stats file + - [an official tool](http://webpack.github.io/analyse/#modules) from webpack authors - [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/) @@ -1074,11 +1070,12 @@ Examples of code that could be shared statically: Examples of code that could **not** be shared statically and how to fix it: - A function that calls a Core service, but does not take that service as a parameter. + - If the function does not take a client as an argument, it must have an instance of the client in its internal state, populated by your plugin. This would not work across plugin boundaries because your plugin would not be able to call `setClient` in the copy of this module in other plugins: ```js let esClient; - export const setClient = (client) => esClient = client; + export const setClient = (client) => (esClient = client); export const query = (params) => esClient.search(params); ``` @@ -1089,6 +1086,7 @@ Examples of code that could **not** be shared statically and how to fix it: ``` - A function that allows other plugins to register values that get pushed into an array defined internally to the module. + - The values registered would only be visible to the plugin that imported it. Each plugin would essentially have their own registry of visTypes that is not visible to any other plugins. ```js @@ -1101,17 +1099,19 @@ Examples of code that could **not** be shared statically and how to fix it: ```js class MyPlugin { - constructor() { this.visTypes = [] } + constructor() { + this.visTypes = []; + } setup() { return { - registerVisType: (visType) => this.visTypes.push(visType) - } + registerVisType: (visType) => this.visTypes.push(visType), + }; } start() { return { - getVisTypes: () => this.visTypes - } + getVisTypes: () => this.visTypes, + }; } } ``` @@ -1144,10 +1144,10 @@ If you have code that should be available to other plugins on both the client an There are some Core services that are purely presentational, for example `core.overlays.openModal()` or `core.application.createLink()` where UI code does need access to these deeply within your application. However, passing these services down as props throughout your application leads to lots of boilerplate. To avoid this, you have three options: 1. Use an abstraction layer, like Redux, to decouple your UI code from core (**this is the highly preferred option**); or - - [redux-thunk](https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument) and [redux-saga](https://redux-saga.js.org/docs/api/#createsagamiddlewareoptions) already have ways to do this. + - [redux-thunk](https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument) and [redux-saga](https://redux-saga.js.org/docs/api/#createsagamiddlewareoptions) already have ways to do this. 2. Use React Context to provide these services to large parts of your React tree; or 3. Create a high-order-component that injects core into a React component; or - - This would be a stateful module that holds a reference to Core, but provides it as props to components with a `withCore(MyComponent)` interface. This can make testing components simpler. (Note: this module cannot be shared across plugin boundaries, see above). + - This would be a stateful module that holds a reference to Core, but provides it as props to components with a `withCore(MyComponent)` interface. This can make testing components simpler. (Note: this module cannot be shared across plugin boundaries, see above). 4. Create a global singleton module that gets imported into each module that needs it. (Note: this module cannot be shared across plugin boundaries, see above). [Example](https://gist.github.com/epixa/06c8eeabd99da3c7545ab295e49acdc3). If you find that you need many different Core services throughout your application, this may be a code smell and could lead to pain down the road. For instance, if you need access to an HTTP Client or SavedObjectsClient in many places in your React tree, it's likely that a data layer abstraction (like Redux) could make developing your plugin much simpler (see option 1). @@ -1281,6 +1281,7 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS _See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-core-server.coresetup.md)_ ##### Plugin services + | Legacy Platform | New Platform | Notes | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----- | | `server.plugins.xpack_main.registerFeature` | [`plugins.features.registerFeature`](x-pack/plugins/features/server/plugin.ts) | | @@ -1332,6 +1333,7 @@ This table shows where these uiExports have moved to in the New Platform. In mos | `visualize` | | | #### Plugin Spec + | Legacy Platform | New Platform | | ----------------------------- | ----------------------------------------------------------------------------------------------------------- | | `id` | [`manifest.id`](/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md) | @@ -1427,6 +1429,7 @@ export class Plugin implements Plugin { ``` All plugins are considered enabled by default. If you want to disable your plugin by default, you could declare the `enabled` flag in plugin config. This is a special Kibana platform key. The platform reads its value and won't create a plugin instance if `enabled: false`. + ```js export const config = { schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), @@ -1435,7 +1438,7 @@ export const config = { #### Handle plugin configuration deprecations -If your plugin have deprecated properties, you can describe them using the `deprecations` config descriptor field. +If your plugin have deprecated properties, you can describe them using the `deprecations` config descriptor field. The system is quite similar to the legacy plugin's deprecation management. The most important difference is that deprecations are managed on a per-plugin basis, meaning that you don't need to specify the whole @@ -1457,7 +1460,7 @@ export const config: PluginConfigDescriptor = { deprecations: ({ rename, unused }) => [ rename('oldProperty', 'newProperty'), unused('someUnusedProperty'), - ] + ], }; ``` @@ -1471,7 +1474,7 @@ export const config: PluginConfigDescriptor = { deprecations: ({ renameFromRoot, unusedFromRoot }) => [ renameFromRoot('oldplugin.property', 'myplugin.property'), unusedFromRoot('oldplugin.deprecated'), - ] + ], }; ``` @@ -1480,22 +1483,25 @@ During migration, if you still need the deprecations to be effective in the lega both plugin definitions. ### Use scoped services + Whenever Kibana needs to get access to data saved in elasticsearch, it should perform a check whether an end-user has access to the data. In the legacy platform, Kibana requires to bind elasticsearch related API with an incoming request to access elasticsearch service on behalf of a user. + ```js - async function handler(req, res) { - const dataCluster = server.plugins.elasticsearch.getCluster('data'); - const data = await dataCluster.callWithRequest(req, 'ping'); - } +async function handler(req, res) { + const dataCluster = server.plugins.elasticsearch.getCluster('data'); + const data = await dataCluster.callWithRequest(req, 'ping'); +} ``` The new platform introduced [a handler interface](/rfcs/text/0003_handler_interface.md) on the server-side to perform that association internally. Core services, that require impersonation with an incoming request, are exposed via `context` argument of [the request handler interface.](/docs/development/core/server/kibana-plugin-core-server.requesthandler.md) The above example looks in the new platform as + ```js - async function handler(context, req, res) { - const data = await context.core.elasticsearch.adminClient.callAsInternalUser('ping') - } +async function handler(context, req, res) { + const data = await context.core.elasticsearch.adminClient.callAsInternalUser('ping'); +} ``` The [request handler context](/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md) exposed the next scoped **core** services: @@ -1508,6 +1514,7 @@ The [request handler context](/docs/development/core/server/kibana-plugin-core-s | `request.getUiSettingsService` | [`context.uiSettings.client`](/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md) | #### Declare a custom scoped service + Plugins can extend the handler context with custom API that will be available to the plugin itself and all dependent plugins. For example, the plugin creates a custom elasticsearch client and want to use it via the request handler context: @@ -1709,4 +1716,68 @@ export class MyPlugin implements Plugin { ``` ### Logging config migration -[Read](./server/logging/README.md#logging-config-migration) \ No newline at end of file + +[Read](./server/logging/README.md#logging-config-migration) + +### Use HashRouter in migrated apps + +Kibana applications are meant to be leveraging the `ScopedHistory` provided in an app's `mount` function to wire their router. For react, +this is done by using the `react-router-dom` `Router` component: + +```typescript +export const renderApp = async (element: HTMLElement, history: ScopedHistory) => { + render( + + + + + + + + , + element + ); + + return () => { + unmountComponentAtNode(element); + unlisten(); + }; +}; +``` + +Some legacy apps were using `react-router-dom`'s `HashRouter` instead. Using `HashRouter` in a migrated application will cause some route change +events to not be catched by the router, as the `BrowserHistory` used behind the provided scoped history does not emit +the `hashevent` that is required for the `HashRouter` to behave correctly. + +It is strictly recommended to migrate your application's routing to browser history, which is the only routing officially supported by the platform. + +However, during the transition period, it is possible to make the two histories cohabitate by manually emitting the required events from +the scoped to the hash history. You may use this workaround at your own risk. While we are not aware of any problems it currently creates, there may be edge cases that do not work properly. + +```typescript +export const renderApp = async (element: HTMLElement, history: ScopedHistory) => { + render( + + + + + + + + , + element + ); + + // dispatch synthetic hash change event to update hash history objects + // this is necessary because hash updates triggered by the scoped history will not emit them. + const unlisten = history.listen(() => { + window.dispatchEvent(new HashChangeEvent('hashchange')); + }); + + return () => { + unmountComponentAtNode(element); + // unsubscribe to `history.listen` when unmounting. + unlisten(); + }; +}; +``` From 29714aa61476972083100a35c9eaecd234b9089f Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 23 Jun 2020 12:12:39 -0700 Subject: [PATCH 14/36] Disabled multiple select for preconfigured connectors to avoid requesting bulk delete on them (#69459) --- .../components/actions_connectors_list.tsx | 1 + .../apps/triggers_actions_ui/connectors.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 6379c4e94866a..5d52896cc628f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -277,6 +277,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { onSelectionChange(updatedSelectedItemsList: ActionConnectorTableItem[]) { setSelectedItems(updatedSelectedItemsList); }, + selectable: ({ isPreconfigured }: ActionConnectorTableItem) => !isPreconfigured, } : undefined } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index 41be67592cbeb..810a80c3401ca 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -189,6 +189,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(await testSubjects.exists('deleteConnector')).to.be(false); expect(await testSubjects.exists('preConfiguredTitleMessage')).to.be(true); + + const checkboxSelectRow = await testSubjects.find('checkboxSelectRow-my-server-log'); + expect(await checkboxSelectRow.getAttribute('disabled')).to.be('true'); }); it('should not be able to edit a preconfigured connector', async () => { From f3fbf31f3a0aa653344f13359f0aa8610f26d701 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Tue, 23 Jun 2020 14:13:21 -0500 Subject: [PATCH 15/36] [chore] TS 3.9: convert ts-ignore to ts-expect-error (#69541) Co-authored-by: Elastic Machine --- .../functions/browser/markdown.ts | 2 +- .../functions/common/compare.ts | 8 +++--- .../functions/common/containerStyle.ts | 2 +- .../functions/common/image.ts | 4 +-- .../functions/common/math.ts | 3 +-- .../functions/common/palette.ts | 2 +- .../canvas_plugin_src/functions/common/pie.ts | 6 ++--- .../functions/common/plot/index.ts | 6 ++--- .../functions/common/render.ts | 1 - .../functions/common/repeatImage.ts | 4 +-- .../functions/common/revealImage.ts | 4 +-- .../functions/common/saved_visualization.ts | 2 +- .../functions/common/staticColumn.ts | 1 - .../functions/server/demodata/index.ts | 2 +- .../functions/server/escount.ts | 2 +- .../functions/server/esdocs.ts | 2 +- .../functions/server/essql.ts | 2 +- .../functions/server/get_field_names.test.ts | 2 +- .../functions/server/pointseries/index.ts | 11 +++----- .../pointseries/lib/is_column_reference.ts | 2 +- .../canvas/canvas_plugin_src/plugin.ts | 9 +++---- .../input_type_to_expression/visualization.ts | 2 +- .../uis/arguments/date_format/index.ts | 1 - .../canvas_plugin_src/uis/arguments/index.ts | 24 ++++++++--------- .../uis/arguments/number_format/index.ts | 1 - .../canvas_plugin_src/uis/views/index.ts | 27 +++++++++---------- .../plugins/canvas/common/lib/autocomplete.ts | 2 +- x-pack/plugins/canvas/common/lib/dataurl.ts | 2 +- x-pack/plugins/canvas/common/lib/index.ts | 26 +++++++----------- .../common/lib/pivot_object_array.test.ts | 2 +- x-pack/plugins/canvas/public/application.tsx | 10 +++---- .../export/__tests__/export_app.test.tsx | 2 +- .../workpad/workpad_app/workpad_telemetry.tsx | 3 --- .../arg_add_popover/arg_add_popover.tsx | 6 ++--- .../public/components/asset_manager/asset.tsx | 1 - .../components/asset_manager/asset_modal.tsx | 2 -- .../public/components/asset_manager/index.ts | 8 +++--- .../components/asset_picker/asset_picker.tsx | 9 +------ .../custom_element_modal.tsx | 2 -- .../components/embeddable_flyout/index.tsx | 2 +- .../components/file_upload/file_upload.tsx | 1 - .../components/font_picker/font_picker.tsx | 1 - .../canvas/public/components/router/index.ts | 4 +-- .../components/saved_elements_modal/index.ts | 5 ++-- .../element_settings/element_settings.tsx | 4 +-- .../components/sidebar/global_config.tsx | 7 +++-- .../public/components/sidebar/sidebar.tsx | 2 +- .../public/components/toolbar/toolbar.tsx | 8 +++--- .../workpad_header/edit_menu/index.ts | 12 ++++----- .../element_menu/element_menu.tsx | 1 - .../workpad_header/element_menu/index.tsx | 4 +-- .../fullscreen_control/fullscreen_control.tsx | 2 +- .../components/workpad_header/index.tsx | 3 --- .../workpad_header/refresh_control/index.ts | 3 +-- .../workpad_header/share_menu/flyout/index.ts | 1 - .../workpad_header/share_menu/utils.ts | 1 - .../workpad_header/view_menu/index.ts | 4 +-- .../workpad_header/workpad_header.tsx | 5 ++-- .../interaction_boundary.tsx | 2 +- .../workpad_shortcuts/workpad_shortcuts.tsx | 2 +- .../extended_template.examples.tsx | 2 +- .../__examples__/simple_template.examples.tsx | 2 +- .../__examples__/simple_template.examples.tsx | 2 +- .../plugins/canvas/public/functions/asset.ts | 2 +- .../canvas/public/functions/filters.ts | 2 +- .../canvas/public/functions/timelion.ts | 2 +- x-pack/plugins/canvas/public/functions/to.ts | 2 +- x-pack/plugins/canvas/public/lib/app_state.ts | 6 ++--- .../public/lib/build_embeddable_filters.ts | 2 +- .../canvas/public/lib/clipboard.test.ts | 2 +- .../canvas/public/lib/clone_subgraphs.ts | 2 +- .../plugins/canvas/public/lib/create_thunk.ts | 2 +- .../canvas/public/lib/download_workpad.ts | 2 +- .../public/lib/element_handler_creators.ts | 2 -- .../plugins/canvas/public/lib/es_service.ts | 1 - .../public/lib/sync_filter_expression.ts | 1 - x-pack/plugins/canvas/public/plugin.tsx | 2 +- x-pack/plugins/canvas/public/registries.ts | 10 +++---- .../canvas/public/state/actions/embeddable.ts | 2 +- .../canvas/public/state/actions/workpad.ts | 2 +- .../__tests__/workpad_autoplay.test.ts | 2 +- .../__tests__/workpad_refresh.test.ts | 1 - .../public/state/middleware/in_flight.ts | 2 +- .../state/middleware/workpad_autoplay.ts | 4 +-- .../state/middleware/workpad_refresh.ts | 5 ++-- .../public/state/reducers/embeddable.ts | 2 +- .../public/state/selectors/resolved_args.ts | 4 +-- .../canvas/public/state/selectors/workpad.ts | 4 +-- x-pack/plugins/canvas/public/store.ts | 4 +-- .../server/routes/es_fields/es_fields.ts | 2 +- .../server/routes/shareables/download.ts | 1 - .../server/sample_data/load_sample_data.ts | 5 ++-- .../api/__tests__/shareable.test.tsx | 2 +- .../rendered_element.examples.tsx | 2 +- .../footer/settings/autoplay_settings.tsx | 1 - .../components/rendered_element.tsx | 6 ++--- .../shareable_runtime/supported_renderers.js | 4 --- .../plugins/canvas/shareable_runtime/types.ts | 2 +- 98 files changed, 164 insertions(+), 220 deletions(-) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts index 41323a82f4ee0..e44fb903ef042 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts @@ -10,7 +10,7 @@ import { Style, ExpressionFunctionDefinition, } from 'src/plugins/expressions/common'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { Handlebars } from '../../../common/lib/handlebars'; import { getFunctionHelp } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.ts index e952faca1d5eb..8a28f71ee1b47 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.ts @@ -59,25 +59,25 @@ export function compare(): ExpressionFunctionDefinition<'compare', Context, Argu return a !== b; case Operation.LT: if (typesMatch) { - // @ts-ignore #35433 This is a wonky comparison for nulls + // @ts-expect-error #35433 This is a wonky comparison for nulls return a < b; } return false; case Operation.LTE: if (typesMatch) { - // @ts-ignore #35433 This is a wonky comparison for nulls + // @ts-expect-error #35433 This is a wonky comparison for nulls return a <= b; } return false; case Operation.GT: if (typesMatch) { - // @ts-ignore #35433 This is a wonky comparison for nulls + // @ts-expect-error #35433 This is a wonky comparison for nulls return a > b; } return false; case Operation.GTE: if (typesMatch) { - // @ts-ignore #35433 This is a wonky comparison for nulls + // @ts-expect-error #35433 This is a wonky comparison for nulls return a >= b; } return false; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts index b841fde284ab6..09ce2b2bf1755 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts @@ -6,7 +6,7 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { ContainerStyle, Overflow, BackgroundRepeat, BackgroundSize } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { isValidUrl } from '../../../common/lib/url'; interface Output extends ContainerStyle { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts index c43ff6373ea0f..3ef956b41ce20 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts @@ -6,9 +6,9 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -// @ts-ignore .png file +// @ts-expect-error .png file import { elasticLogo } from '../../lib/elastic_logo'; export enum ImageMode { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts index 7f84dc54d8092..e36644530eae8 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore no @typed def; Elastic library +// @ts-expect-error no @typed def; Elastic library import { evaluate } from 'tinymath'; -// @ts-ignore untyped local import { pivotObjectArray } from '../../../common/lib/pivot_object_array'; import { Datatable, isDatatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts index 63cd663d2ac4c..f27abe261e2e2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts @@ -5,7 +5,7 @@ */ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { palettes } from '../../../common/lib/palettes'; import { getFunctionHelp } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts index 6cb64a43ea582..b568f18924869 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts @@ -5,11 +5,11 @@ */ import { get, map, groupBy } from 'lodash'; -// @ts-ignore lodash.keyby imports invalid member from @types/lodash +// @ts-expect-error lodash.keyby imports invalid member from @types/lodash import keyBy from 'lodash.keyby'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { getColorsFromPalette } from '../../../common/lib/get_colors_from_palette'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { getLegendConfig } from '../../../common/lib/get_legend_config'; import { getFunctionHelp } from '../../../i18n'; import { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts index e8214ca8eaf9f..0b4583f4581ae 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore no @typed def +// @ts-expect-error no @typed def import keyBy from 'lodash.keyby'; import { groupBy, get, set, map, sortBy } from 'lodash'; import { ExpressionFunctionDefinition, Style } from 'src/plugins/expressions'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { getColorsFromPalette } from '../../../../common/lib/get_colors_from_palette'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { getLegendConfig } from '../../../../common/lib/get_legend_config'; import { getFlotAxisConfig } from './get_flot_axis_config'; import { getFontSpec } from './get_font_spec'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts index da50195480c68..f8eeabfccde6d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts @@ -7,7 +7,6 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Render, ContainerStyle } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; -// @ts-ignore unconverted local file import { DEFAULT_ELEMENT_CSS } from '../../../common/lib/constants'; interface ContainerStyleArgument extends ContainerStyle { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts index f91fd3dfc5522..9e296f2b9a92a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts @@ -5,9 +5,9 @@ */ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -// @ts-ignore .png file +// @ts-expect-error .png file import { elasticOutline } from '../../lib/elastic_outline'; import { Render } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts index d961227a302b8..3e721cc49b411 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts @@ -5,9 +5,9 @@ */ import { ExpressionFunctionDefinition, ExpressionValueRender } from 'src/plugins/expressions'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -// @ts-ignore .png file +// @ts-expect-error .png file import { elasticOutline } from '../../lib/elastic_outline'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts index 83663dd2a00ad..2782ca039d7ed 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts @@ -78,7 +78,7 @@ export function savedVisualization(): ExpressionFunctionDefinition< } if (hideLegend === true) { - // @ts-ignore LegendOpen missing on VisualizeInput + // @ts-expect-error LegendOpen missing on VisualizeInput visOptions.legendOpen = false; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts index 9dd38dd57c677..4fa4be0a2f09f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore untyped Elastic library import { getType } from '@kbn/interpreter/common'; import { ExpressionFunctionDefinition, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts index 843e2bda47e12..60d5edeb10483 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts @@ -6,7 +6,7 @@ import { sortBy } from 'lodash'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; -// @ts-ignore unconverted lib file +// @ts-expect-error unconverted lib file import { queryDatatable } from '../../../../common/lib/datatable/query'; import { DemoRows } from './demo_rows_types'; import { getDemoRows } from './get_demo_rows'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts index 142331aabf351..26f651e770363 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts @@ -9,7 +9,7 @@ import { ExpressionValueFilter, } from 'src/plugins/expressions/common'; /* eslint-disable */ -// @ts-ignore untyped local +// @ts-expect-error untyped local import { buildESRequest } from '../../../server/lib/build_es_request'; /* eslint-enable */ import { getFunctionHelp } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts index 2b229b8957ec1..a090f09a76ea2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts @@ -7,7 +7,7 @@ import squel from 'squel'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; /* eslint-disable */ -// @ts-ignore untyped local +// @ts-expect-error untyped local import { queryEsSQL } from '../../../server/lib/query_es_sql'; /* eslint-enable */ import { ExpressionValueFilter } from '../../../types'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts index c64398d4b3a18..5ac91bec849c2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts @@ -6,7 +6,7 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; /* eslint-disable */ -// @ts-ignore untyped local +// @ts-expect-error untyped local import { queryEsSQL } from '../../../server/lib/query_es_sql'; /* eslint-enable */ import { ExpressionValueFilter } from '../../../types'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts index c25628e5cf2b9..7dee587895485 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore untyped library +// @ts-expect-error untyped library import { parse } from 'tinymath'; import { getFieldNames } from './pointseries/lib/get_field_names'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts index 54e48c8abf04b..bae80d3c33510 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore Untyped library +// @ts-expect-error untyped library import uniqBy from 'lodash.uniqby'; -// @ts-ignore Untyped Elastic library +// @ts-expect-error untyped Elastic library import { evaluate } from 'tinymath'; import { groupBy, zipObject, omit } from 'lodash'; import moment from 'moment'; @@ -18,13 +18,10 @@ import { PointSeriesColumnName, PointSeriesColumns, } from 'src/plugins/expressions/common'; -// @ts-ignore Untyped local import { pivotObjectArray } from '../../../../common/lib/pivot_object_array'; -// @ts-ignore Untyped local import { unquoteString } from '../../../../common/lib/unquote_string'; -// @ts-ignore Untyped local import { isColumnReference } from './lib/is_column_reference'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { getExpressionType } from './lib/get_expression_type'; import { getFunctionHelp, getFunctionErrors } from '../../../../i18n'; @@ -125,7 +122,7 @@ export function pointseries(): ExpressionFunctionDefinition< col.role = 'measure'; } - // @ts-ignore untyped local: get_expression_type + // @ts-expect-error untyped local: get_expression_type columns[argName] = col; } }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts index 0ecc135ba9042..aed9861e1250c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore Untyped Library +// @ts-expect-error untyped library import { parse } from 'tinymath'; export function isColumnReference(mathExpression: string | null): boolean { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts index c9ce4d065968a..4fbb5d0069e51 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts @@ -12,17 +12,16 @@ import { Start as InspectorStart } from '../../../../src/plugins/inspector/publi import { functions } from './functions/browser'; import { typeFunctions } from './expression_types'; -// @ts-ignore: untyped local +// @ts-expect-error: untyped local import { renderFunctions, renderFunctionFactories } from './renderers'; import { initializeElements } from './elements'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { transformSpecs } from './uis/transforms'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { datasourceSpecs } from './uis/datasources'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { modelSpecs } from './uis/models'; import { initializeViews } from './uis/views'; -// @ts-ignore Untyped Local import { initializeArgs } from './uis/arguments'; import { tagSpecs } from './uis/tags'; import { templateSpecs } from './templates'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts index 4c8de2afd81ad..f03c10e2d424e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts @@ -26,7 +26,7 @@ export function toExpression(input: VisualizeInput): string { .reduce((_, part) => expressionParts.push(part), 0); } - // @ts-ignore LegendOpen missing on VisualizeInput type + // @ts-expect-error LegendOpen missing on VisualizeInput type if (input.vis?.legendOpen !== undefined && input.vis.legendOpen === false) { expressionParts.push(`hideLegend=true`); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts index fce9b21fa0387..e972928fe20b0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts @@ -7,7 +7,6 @@ import { compose, withProps } from 'recompose'; import moment from 'moment'; import { DateFormatArgInput as Component, Props as ComponentProps } from './date_format'; -// @ts-ignore untyped local lib import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { ArgumentFactory } from '../../../../types/arguments'; import { ArgumentStrings } from '../../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts index 2f9a21d8a009f..94a9cf28aef69 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts @@ -5,31 +5,31 @@ */ import { axisConfig } from './axis_config'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { datacolumn } from './datacolumn'; import { dateFormatInitializer } from './date_format'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { filterGroup } from './filter_group'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { imageUpload } from './image_upload'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { number } from './number'; import { numberFormatInitializer } from './number_format'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { palette } from './palette'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { percentage } from './percentage'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { range } from './range'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { select } from './select'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { shape } from './shape'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { string } from './string'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { textarea } from './textarea'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { toggle } from './toggle'; import { SetupInitializer } from '../../plugin'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts index 5a3e3904f4f23..17d630f0ab9e2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts @@ -6,7 +6,6 @@ import { compose, withProps } from 'recompose'; import { NumberFormatArgInput as Component, Props as ComponentProps } from './number_format'; -// @ts-ignore untyped local lib import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { ArgumentFactory } from '../../../../types/arguments'; import { ArgumentStrings } from '../../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts index 34877f2fd551b..19f10628a90cb 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts @@ -4,33 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore untyped local +// @ts-expect-error untyped local import { dropdownControl } from './dropdownControl'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { getCell } from './getCell'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { image } from './image'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { markdown } from './markdown'; -// @ts-ignore untyped local import { metricInitializer } from './metric'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { pie } from './pie'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { plot } from './plot'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { progress } from './progress'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { repeatImage } from './repeatImage'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { revealImage } from './revealImage'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { render } from './render'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { shape } from './shape'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { table } from './table'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { timefilterControl } from './timefilterControl'; import { SetupInitializer } from '../../plugin'; diff --git a/x-pack/plugins/canvas/common/lib/autocomplete.ts b/x-pack/plugins/canvas/common/lib/autocomplete.ts index c97879de2137e..0a30b2e2f598e 100644 --- a/x-pack/plugins/canvas/common/lib/autocomplete.ts +++ b/x-pack/plugins/canvas/common/lib/autocomplete.ts @@ -5,7 +5,7 @@ */ import { uniq } from 'lodash'; -// @ts-ignore Untyped Library +// @ts-expect-error untyped library import { parse } from '@kbn/interpreter/common'; import { ExpressionAstExpression, diff --git a/x-pack/plugins/canvas/common/lib/dataurl.ts b/x-pack/plugins/canvas/common/lib/dataurl.ts index ea5a26b27e423..60e65a6d3ca1c 100644 --- a/x-pack/plugins/canvas/common/lib/dataurl.ts +++ b/x-pack/plugins/canvas/common/lib/dataurl.ts @@ -6,7 +6,7 @@ import { fromByteArray } from 'base64-js'; -// @ts-ignore @types/mime doesn't resolve mime/lite for some reason. +// @ts-expect-error @types/mime doesn't resolve mime/lite for some reason. import mime from 'mime/lite'; const dataurlRegex = /^data:([a-z]+\/[a-z0-9-+.]+)(;[a-z-]+=[a-z0-9-]+)?(;([a-z0-9]+))?,/; diff --git a/x-pack/plugins/canvas/common/lib/index.ts b/x-pack/plugins/canvas/common/lib/index.ts index 5ab29c290c3da..4cb3cbbb9b4e6 100644 --- a/x-pack/plugins/canvas/common/lib/index.ts +++ b/x-pack/plugins/canvas/common/lib/index.ts @@ -4,39 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './datatable'; -// @ts-ignore missing local definition export * from './autocomplete'; export * from './constants'; export * from './dataurl'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './errors'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './expression_form_handlers'; -// @ts-ignore missing local definition export * from './fetch'; export * from './fonts'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './get_colors_from_palette'; -// @ts-ignore missing local definition export * from './get_field_type'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './get_legend_config'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './handlebars'; export * from './hex_to_rgb'; -// @ts-ignore missing local definition export * from './httpurl'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './missing_asset'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './palettes'; -// @ts-ignore missing local definition export * from './pivot_object_array'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './resolve_dataurl'; -// @ts-ignore missing local definition export * from './unquote_string'; -// @ts-ignore missing local definition +// @ts-expect-error missing local definition export * from './url'; diff --git a/x-pack/plugins/canvas/common/lib/pivot_object_array.test.ts b/x-pack/plugins/canvas/common/lib/pivot_object_array.test.ts index faf319769cab0..0fbc2fa6b0f38 100644 --- a/x-pack/plugins/canvas/common/lib/pivot_object_array.test.ts +++ b/x-pack/plugins/canvas/common/lib/pivot_object_array.test.ts @@ -55,7 +55,7 @@ describe('pivotObjectArray', () => { }); it('throws when given an invalid column list', () => { - // @ts-ignore testing potential calls from legacy code that should throw + // @ts-expect-error testing potential calls from legacy code that should throw const check = () => pivotObjectArray(rows, [{ name: 'price' }, { name: 'missing' }]); expect(check).toThrowError('Columns should be an array of strings'); }); diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx index c799f36a283c1..b2c836fe4805f 100644 --- a/x-pack/plugins/canvas/public/application.tsx +++ b/x-pack/plugins/canvas/public/application.tsx @@ -15,14 +15,14 @@ import { BehaviorSubject } from 'rxjs'; import { AppMountParameters, CoreStart, CoreSetup, AppUpdater } from 'kibana/public'; import { CanvasStartDeps, CanvasSetupDeps } from './plugin'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { App } from './components/app'; import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; import { registerLanguage } from './lib/monaco_language_def'; import { SetupRegistries } from './plugin_api'; import { initRegistries, populateRegistries, destroyRegistries } from './registries'; import { getDocumentationLinks } from './lib/documentation_links'; -// @ts-ignore untyped component +// @ts-expect-error untyped component import { HelpMenu } from './components/help_menu/help_menu'; import { createStore } from './store'; @@ -32,12 +32,12 @@ import { init as initStatsReporter } from './lib/ui_metric'; import { CapabilitiesStrings } from '../i18n'; import { startServices, services } from './services'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { createHistory, destroyHistory } from './lib/history_provider'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { stopRouter } from './lib/router_provider'; import { initFunctions } from './functions'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { appUnload } from './state/actions/app'; import './style/index.scss'; diff --git a/x-pack/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx b/x-pack/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx index 7f5b53df4ba52..b0a8d1e990e75 100644 --- a/x-pack/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx +++ b/x-pack/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { mount } from 'enzyme'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { ExportApp } from '../export_app'; jest.mock('style-it', () => ({ diff --git a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx index 3014369d94857..981334ff8d9f2 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx +++ b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx @@ -6,11 +6,8 @@ import React, { useState, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -// @ts-ignore: Local Untyped import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric'; -// @ts-ignore: Local Untyped import { getElementCounts } from '../../../state/selectors/workpad'; -// @ts-ignore: Local Untyped import { getArgs } from '../../../state/selectors/resolved_args'; const WorkpadLoadedMetric = 'workpad-loaded'; diff --git a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx index c26fdb8c46d0f..26295acecd920 100644 --- a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx +++ b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx @@ -7,11 +7,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EuiButtonIcon } from '@elastic/eui'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { Popover, PopoverChildrenProps } from '../popover'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { ArgAdd } from '../arg_add'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { Arg } from '../../expression_types/arg'; import { ComponentStrings } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset.tsx index cb7ec1aba8f59..b0eaecc7b5203 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset.tsx +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset.tsx @@ -7,7 +7,6 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, - // @ts-ignore (elastic/eui#1262) EuiImage is not exported yet EuiImage, EuiPanel, EuiSpacer, diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx index c02fc440abb0b..cb61bf1dc26c4 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx @@ -6,7 +6,6 @@ import { EuiButton, EuiEmptyPrompt, - // @ts-ignore (elastic/eui#1557) EuiFilePicker is not exported yet EuiFilePicker, EuiFlexGrid, EuiFlexGroup, @@ -27,7 +26,6 @@ import React, { FunctionComponent } from 'react'; import { ComponentStrings } from '../../../i18n'; -// @ts-ignore import { ASSET_MAX_SIZE } from '../../../common/lib/constants'; import { Loading } from '../loading'; import { Asset } from './asset'; diff --git a/x-pack/plugins/canvas/public/components/asset_manager/index.ts b/x-pack/plugins/canvas/public/components/asset_manager/index.ts index 23dbe3df085d4..b07857f13f6c6 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/index.ts +++ b/x-pack/plugins/canvas/public/components/asset_manager/index.ts @@ -9,16 +9,16 @@ import { compose, withProps } from 'recompose'; import { set, get } from 'lodash'; import { fromExpression, toExpression } from '@kbn/interpreter/common'; import { getAssets } from '../../state/selectors/assets'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { removeAsset, createAsset } from '../../state/actions/assets'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { elementsRegistry } from '../../lib/elements_registry'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { addElement } from '../../state/actions/elements'; import { getSelectedPage } from '../../state/selectors/workpad'; import { encode } from '../../../common/lib/dataurl'; import { getId } from '../../lib/get_id'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { findExistingAsset } from '../../lib/find_existing_asset'; import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; import { withKibana } from '../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx b/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx index 4489e877abf88..1f49e9ae14f5c 100644 --- a/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx +++ b/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx @@ -6,14 +6,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { - EuiFlexGrid, - EuiFlexItem, - EuiLink, - // @ts-ignore (elastic/eui#1557) EuiImage is not exported yet - EuiImage, - EuiIcon, -} from '@elastic/eui'; +import { EuiFlexGrid, EuiFlexItem, EuiLink, EuiImage, EuiIcon } from '@elastic/eui'; import { CanvasAsset } from '../../../types'; diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx index 8f73939de69a6..ceb7c83f3cab5 100644 --- a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx +++ b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx @@ -12,7 +12,6 @@ import { EuiButton, EuiButtonEmpty, EuiFieldText, - // @ts-ignore hasn't been converted to TypeScript yet EuiFilePicker, EuiFlexGroup, EuiFlexItem, @@ -27,7 +26,6 @@ import { EuiTextArea, EuiTitle, } from '@elastic/eui'; -// @ts-ignore converting /libs/constants to TS breaks CI import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; import { encode } from '../../../common/lib/dataurl'; import { ElementCard } from '../element_card'; diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/index.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/index.tsx index 8e69396f67c2e..9462ba0411de4 100644 --- a/x-pack/plugins/canvas/public/components/embeddable_flyout/index.tsx +++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/index.tsx @@ -10,7 +10,7 @@ import { compose } from 'recompose'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { AddEmbeddableFlyout, Props } from './flyout'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { addElement } from '../../state/actions/elements'; import { getSelectedPage } from '../../state/selectors/workpad'; import { EmbeddableTypes } from '../../../canvas_plugin_src/expression_types/embeddable'; diff --git a/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx b/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx index 993ee8bde2653..22fa32606407b 100644 --- a/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx +++ b/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore (elastic/eui#1262) EuiFilePicker is not exported yet import { EuiFilePicker } from '@elastic/eui'; import PropTypes from 'prop-types'; import React, { FunctionComponent } from 'react'; diff --git a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx index 4340430829342..556a3c5452160 100644 --- a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx +++ b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore (elastic/eui#1262) EuiSuperSelect is not exported yet import { EuiSuperSelect } from '@elastic/eui'; import PropTypes from 'prop-types'; import React, { FunctionComponent } from 'react'; diff --git a/x-pack/plugins/canvas/public/components/router/index.ts b/x-pack/plugins/canvas/public/components/router/index.ts index fa857c6f0cd3c..561ad0e9401f5 100644 --- a/x-pack/plugins/canvas/public/components/router/index.ts +++ b/x-pack/plugins/canvas/public/components/router/index.ts @@ -5,14 +5,14 @@ */ import { connect } from 'react-redux'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { setFullscreen } from '../../state/actions/transient'; import { enableAutoplay, setRefreshInterval, setAutoplayInterval, } from '../../state/actions/workpad'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { Router as Component } from './router'; import { State } from '../../../types'; diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/index.ts b/x-pack/plugins/canvas/public/components/saved_elements_modal/index.ts index f14fc92e028db..c5c1dbc2fdd6e 100644 --- a/x-pack/plugins/canvas/public/components/saved_elements_modal/index.ts +++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/index.ts @@ -8,14 +8,13 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { compose, withState } from 'recompose'; import { camelCase } from 'lodash'; -// @ts-ignore Untyped local import { cloneSubgraphs } from '../../lib/clone_subgraphs'; import * as customElementService from '../../lib/custom_element_service'; import { withKibana } from '../../../../../../src/plugins/kibana_react/public'; import { WithKibanaProps } from '../../'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { selectToplevelNodes } from '../../state/actions/transient'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { insertNodes } from '../../state/actions/elements'; import { getSelectedPage } from '../../state/selectors/workpad'; import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric'; diff --git a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx index 74f4887601d30..e3f4e00f4de01 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx @@ -7,9 +7,9 @@ import React, { FunctionComponent } from 'react'; import PropTypes from 'prop-types'; import { EuiTabbedContent } from '@elastic/eui'; -// @ts-ignore unconverted component +// @ts-expect-error unconverted component import { Datasource } from '../../datasource'; -// @ts-ignore unconverted component +// @ts-expect-error unconverted component import { FunctionFormList } from '../../function_form_list'; import { PositionedElement } from '../../../../types'; import { ComponentStrings } from '../../../../i18n'; diff --git a/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx b/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx index 2e241681ccc6a..f89ab79a086cf 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx @@ -5,13 +5,12 @@ */ import React, { Fragment, FunctionComponent } from 'react'; -// @ts-ignore unconverted component +// @ts-expect-error unconverted component import { ElementConfig } from '../element_config'; -// @ts-ignore unconverted component +// @ts-expect-error unconverted component import { PageConfig } from '../page_config'; -// @ts-ignore unconverted component import { WorkpadConfig } from '../workpad_config'; -// @ts-ignore unconverted component +// @ts-expect-error unconverted component import { SidebarSection } from './sidebar_section'; export const GlobalConfig: FunctionComponent = () => ( diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx b/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx index 26f106911e015..9f1936fdc143b 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx @@ -5,7 +5,7 @@ */ import React, { FunctionComponent } from 'react'; -// @ts-ignore unconverted component +// @ts-expect-error unconverted component import { SidebarContent } from './sidebar_content'; interface Props { diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.tsx b/x-pack/plugins/canvas/public/components/toolbar/toolbar.tsx index 0f8204e6bc261..9a26b438e17c3 100644 --- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.tsx +++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.tsx @@ -20,13 +20,13 @@ import { CanvasElement } from '../../../types'; import { ComponentStrings } from '../../../i18n'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { Navbar } from '../navbar'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { WorkpadManager } from '../workpad_manager'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { PageManager } from '../page_manager'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { Expression } from '../expression'; import { Tray } from './tray'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts index 75bdcd2b0ada1..8f013f70aefcd 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts @@ -9,17 +9,17 @@ import { compose, withHandlers, withProps } from 'recompose'; import { Dispatch } from 'redux'; import { State, PositionedElement } from '../../../../types'; import { getClipboardData } from '../../../lib/clipboard'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { flatten } from '../../../lib/aeroelastic/functional'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { globalStateUpdater } from '../../workpad_page/integration_utils'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { crawlTree } from '../../workpad_page/integration_utils'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { insertNodes, elementLayer, removeElements } from '../../../state/actions/elements'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { undoHistory, redoHistory } from '../../../state/actions/history'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { selectToplevelNodes } from '../../../state/actions/transient'; import { getSelectedPage, diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx index fbb5d70dfc55c..6d9233aaba22b 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx @@ -19,7 +19,6 @@ import { ElementSpec } from '../../../../types'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; import { getId } from '../../../lib/get_id'; import { Popover, ClosePopoverFn } from '../../popover'; -// @ts-ignore Untyped local import { AssetManager } from '../../asset_manager'; import { SavedElementsModal } from '../../saved_elements_modal'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/index.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/index.tsx index a1227b3394678..13b2cace13a40 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/index.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/index.tsx @@ -10,10 +10,10 @@ import { compose, withProps } from 'recompose'; import { Dispatch } from 'redux'; import { withKibana } from '../../../../../../../src/plugins/kibana_react/public/'; import { State, ElementSpec } from '../../../../types'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { elementsRegistry } from '../../../lib/elements_registry'; import { ElementMenu as Component, Props as ComponentProps } from './element_menu'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { addElement } from '../../../state/actions/elements'; import { getSelectedPage } from '../../../state/selectors/workpad'; import { AddEmbeddablePanel } from '../../embeddable_flyout'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.tsx b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.tsx index 5ffa712abee13..77edf9d2264d4 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.tsx @@ -6,7 +6,7 @@ import React, { ReactNode, KeyboardEvent } from 'react'; import PropTypes from 'prop-types'; -// @ts-ignore no @types definition +// @ts-expect-error no @types definition import { Shortcuts } from 'react-shortcuts'; import { isTextInput } from '../../../lib/is_text_input'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/index.tsx b/x-pack/plugins/canvas/public/components/workpad_header/index.tsx index d2fece567a8ad..407b4ff932811 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/index.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/index.tsx @@ -6,11 +6,8 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -// @ts-ignore untyped local import { canUserWrite } from '../../state/selectors/app'; -// @ts-ignore untyped local import { getSelectedPage, isWriteable } from '../../state/selectors/workpad'; -// @ts-ignore untyped local import { setWriteable } from '../../state/actions/workpad'; import { State } from '../../../types'; import { WorkpadHeader as Component, Props as ComponentProps } from './workpad_header'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/index.ts index 53c053811a273..87b926d93ccb9 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/index.ts @@ -5,9 +5,8 @@ */ import { connect } from 'react-redux'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { fetchAllRenderables } from '../../../state/actions/elements'; -// @ts-ignore untyped local import { getInFlight } from '../../../state/selectors/resolved_args'; import { State } from '../../../../types'; import { RefreshControl as Component } from './refresh_control'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts index 64712f0df8d6c..1e1eac2a1dcf3 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts @@ -11,7 +11,6 @@ import { getRenderedWorkpad, getRenderedWorkpadExpressions, } from '../../../../state/selectors/workpad'; -// @ts-ignore Untyped local import { downloadRenderedWorkpad, downloadRuntime, diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts index 8a3438e89e846..45257cd4fe308 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts @@ -5,7 +5,6 @@ */ import rison from 'rison-node'; -// @ts-ignore Untyped local. import { IBasePath } from 'kibana/public'; import { fetch } from '../../../../common/lib/fetch'; import { CanvasWorkpad } from '../../../../types'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts index 0765973915f77..ddf1a12775cae 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts @@ -10,9 +10,9 @@ import { Dispatch } from 'redux'; import { withKibana } from '../../../../../../../src/plugins/kibana_react/public/'; import { zoomHandlerCreators } from '../../../lib/app_handler_creators'; import { State, CanvasWorkpadBoundingBox } from '../../../../types'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { fetchAllRenderables } from '../../../state/actions/elements'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { setZoomScale, setFullscreen, selectToplevelNodes } from '../../../state/actions/transient'; import { setWriteable, diff --git a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx index 4aab8280a9f24..eb4b451896b46 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx @@ -6,14 +6,13 @@ import React, { FunctionComponent } from 'react'; import PropTypes from 'prop-types'; -// @ts-ignore no @types definition +// @ts-expect-error no @types definition import { Shortcuts } from 'react-shortcuts'; import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { ComponentStrings } from '../../../i18n'; import { ToolTipShortcut } from '../tool_tip_shortcut/'; -// @ts-ignore untyped local import { RefreshControl } from './refresh_control'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { FullscreenControl } from './fullscreen_control'; import { EditMenu } from './edit_menu'; import { ElementMenu } from './element_menu'; diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx index d5841a1069ea1..e1ed7c7db84a0 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx @@ -5,7 +5,7 @@ */ import React, { CSSProperties, PureComponent } from 'react'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { WORKPAD_CONTAINER_ID } from '../../../apps/workpad/workpad_app'; interface State { diff --git a/x-pack/plugins/canvas/public/components/workpad_shortcuts/workpad_shortcuts.tsx b/x-pack/plugins/canvas/public/components/workpad_shortcuts/workpad_shortcuts.tsx index f9e0ec8a8a541..1bb3ef330f846 100644 --- a/x-pack/plugins/canvas/public/components/workpad_shortcuts/workpad_shortcuts.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_shortcuts/workpad_shortcuts.tsx @@ -7,7 +7,7 @@ import React, { Component, KeyboardEvent } from 'react'; import isEqual from 'react-fast-compare'; -// @ts-ignore no @types definition +// @ts-expect-error no @types definition import { Shortcuts } from 'react-shortcuts'; import { isTextInput } from '../../lib/is_text_input'; diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx index 5fdc88ed62406..863cdd88163c2 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx @@ -7,7 +7,7 @@ import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import React from 'react'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { getDefaultWorkpad } from '../../../../state/defaults'; import { Arguments, ArgumentTypes, BorderStyle, ExtendedTemplate } from '../extended_template'; diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx index 4ef17fbe87616..2dbff1b4d916b 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx @@ -7,7 +7,7 @@ import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import React from 'react'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { getDefaultWorkpad } from '../../../../state/defaults'; import { Argument, Arguments, SimpleTemplate } from '../simple_template'; diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx index f9b175e84ec8e..fa1b2420d46d2 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx @@ -7,7 +7,7 @@ import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import React from 'react'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { getDefaultWorkpad } from '../../../../state/defaults'; import { SimpleTemplate } from '../simple_template'; diff --git a/x-pack/plugins/canvas/public/functions/asset.ts b/x-pack/plugins/canvas/public/functions/asset.ts index 2f2ad181b264c..ebd3fd2abdcbb 100644 --- a/x-pack/plugins/canvas/public/functions/asset.ts +++ b/x-pack/plugins/canvas/public/functions/asset.ts @@ -5,7 +5,7 @@ */ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public'; -// @ts-ignore unconverted local lib +// @ts-expect-error unconverted local lib import { getState } from '../state/store'; import { getAssetById } from '../state/selectors/assets'; import { getFunctionHelp, getFunctionErrors } from '../../i18n'; diff --git a/x-pack/plugins/canvas/public/functions/filters.ts b/x-pack/plugins/canvas/public/functions/filters.ts index 78cd742b44b26..48f4a41c7690a 100644 --- a/x-pack/plugins/canvas/public/functions/filters.ts +++ b/x-pack/plugins/canvas/public/functions/filters.ts @@ -8,7 +8,7 @@ import { fromExpression } from '@kbn/interpreter/common'; import { get } from 'lodash'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public'; import { interpretAst } from '../lib/run_interpreter'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { getState } from '../state/store'; import { getGlobalFilters } from '../state/selectors/workpad'; import { ExpressionValueFilter } from '../../types'; diff --git a/x-pack/plugins/canvas/public/functions/timelion.ts b/x-pack/plugins/canvas/public/functions/timelion.ts index abb294d9cc110..4eb34e838d18a 100644 --- a/x-pack/plugins/canvas/public/functions/timelion.ts +++ b/x-pack/plugins/canvas/public/functions/timelion.ts @@ -9,7 +9,7 @@ import moment from 'moment-timezone'; import { TimeRange } from 'src/plugins/data/common'; import { ExpressionFunctionDefinition, DatatableRow } from 'src/plugins/expressions/public'; import { fetch } from '../../common/lib/fetch'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { buildBoolArray } from '../../public/lib/build_bool_array'; import { Datatable, ExpressionValueFilter } from '../../types'; import { getFunctionHelp } from '../../i18n'; diff --git a/x-pack/plugins/canvas/public/functions/to.ts b/x-pack/plugins/canvas/public/functions/to.ts index 64d25b28a8aa0..032873dfa6cf2 100644 --- a/x-pack/plugins/canvas/public/functions/to.ts +++ b/x-pack/plugins/canvas/public/functions/to.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore untyped Elastic library +// @ts-expect-error untyped Elastic library import { castProvider } from '@kbn/interpreter/common'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public'; import { getFunctionHelp, getFunctionErrors } from '../../i18n'; diff --git a/x-pack/plugins/canvas/public/lib/app_state.ts b/x-pack/plugins/canvas/public/lib/app_state.ts index d431202ba75a4..a09df3c8cb87d 100644 --- a/x-pack/plugins/canvas/public/lib/app_state.ts +++ b/x-pack/plugins/canvas/public/lib/app_state.ts @@ -6,12 +6,12 @@ import { parse } from 'query-string'; import { get } from 'lodash'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { getInitialState } from '../state/initial_state'; import { getWindow } from './get_window'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { historyProvider } from './history_provider'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { routerProvider } from './router_provider'; import { createTimeInterval, isValidTimeInterval, getTimeInterval } from './time_interval'; import { AppState, AppStateKeys } from '../../types'; diff --git a/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts b/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts index c847bfb6516bf..94d0d16bf79f6 100644 --- a/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts +++ b/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts @@ -5,7 +5,7 @@ */ import { ExpressionValueFilter } from '../../types'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { buildBoolArray } from './build_bool_array'; import { TimeRange, esFilters, Filter as DataFilter } from '../../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/canvas/public/lib/clipboard.test.ts b/x-pack/plugins/canvas/public/lib/clipboard.test.ts index d10964003ed39..53f92e2184edc 100644 --- a/x-pack/plugins/canvas/public/lib/clipboard.test.ts +++ b/x-pack/plugins/canvas/public/lib/clipboard.test.ts @@ -15,7 +15,7 @@ const get = jest.fn(); describe('clipboard', () => { beforeAll(() => { - // @ts-ignore + // @ts-expect-error Storage.mockImplementation(() => ({ set, get, diff --git a/x-pack/plugins/canvas/public/lib/clone_subgraphs.ts b/x-pack/plugins/canvas/public/lib/clone_subgraphs.ts index c3a3933e06a6d..7168272211d44 100644 --- a/x-pack/plugins/canvas/public/lib/clone_subgraphs.ts +++ b/x-pack/plugins/canvas/public/lib/clone_subgraphs.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { arrayToMap } from './aeroelastic/functional'; import { getId } from './get_id'; import { PositionedElement } from '../../types'; diff --git a/x-pack/plugins/canvas/public/lib/create_thunk.ts b/x-pack/plugins/canvas/public/lib/create_thunk.ts index cbcaeeccc8b93..8ce912246ad6f 100644 --- a/x-pack/plugins/canvas/public/lib/create_thunk.ts +++ b/x-pack/plugins/canvas/public/lib/create_thunk.ts @@ -5,7 +5,7 @@ */ import { Dispatch, Action } from 'redux'; -// @ts-ignore untyped dependency +// @ts-expect-error untyped dependency import { createThunk as createThunkFn } from 'redux-thunks/cjs'; import { State } from '../../types'; diff --git a/x-pack/plugins/canvas/public/lib/download_workpad.ts b/x-pack/plugins/canvas/public/lib/download_workpad.ts index fb038d8b6ace2..d0a63cf3fb5c4 100644 --- a/x-pack/plugins/canvas/public/lib/download_workpad.ts +++ b/x-pack/plugins/canvas/public/lib/download_workpad.ts @@ -7,7 +7,7 @@ import fileSaver from 'file-saver'; import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../common/lib/constants'; import { ErrorStrings } from '../../i18n'; import { notifyService } from '../services'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import * as workpadService from './workpad_service'; import { CanvasRenderedWorkpad } from '../../shareable_runtime/types'; diff --git a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts index a2bf5a62ec1f7..8f1a0f0ecf08f 100644 --- a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts +++ b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts @@ -5,9 +5,7 @@ */ import { camelCase } from 'lodash'; -// @ts-ignore unconverted local file import { getClipboardData, setClipboardData } from './clipboard'; -// @ts-ignore unconverted local file import { cloneSubgraphs } from './clone_subgraphs'; import { notifyService } from '../services'; import * as customElementService from './custom_element_service'; diff --git a/x-pack/plugins/canvas/public/lib/es_service.ts b/x-pack/plugins/canvas/public/lib/es_service.ts index 496751a874b21..5c1131d5fbe35 100644 --- a/x-pack/plugins/canvas/public/lib/es_service.ts +++ b/x-pack/plugins/canvas/public/lib/es_service.ts @@ -7,7 +7,6 @@ import { IndexPatternAttributes } from 'src/plugins/data/public'; import { API_ROUTE } from '../../common/lib/constants'; -// @ts-ignore untyped local import { fetch } from '../../common/lib/fetch'; import { ErrorStrings } from '../../i18n'; import { notifyService } from '../services'; diff --git a/x-pack/plugins/canvas/public/lib/sync_filter_expression.ts b/x-pack/plugins/canvas/public/lib/sync_filter_expression.ts index dc70f778f0e52..4bfe6ff4b141f 100644 --- a/x-pack/plugins/canvas/public/lib/sync_filter_expression.ts +++ b/x-pack/plugins/canvas/public/lib/sync_filter_expression.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore internal untyped import { fromExpression } from '@kbn/interpreter/common'; import immutable from 'object-path-immutable'; import { get } from 'lodash'; diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index 9d2a6b3fdf4f4..4829a94bb0db8 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -24,7 +24,7 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { Start as InspectorStart } from '../../../../src/plugins/inspector/public'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { argTypeSpecs } from './expression_types/arg_types'; import { transitions } from './transitions'; import { getPluginApi, CanvasApi } from './plugin_api'; diff --git a/x-pack/plugins/canvas/public/registries.ts b/x-pack/plugins/canvas/public/registries.ts index 99f309a917329..b2881fc0b7799 100644 --- a/x-pack/plugins/canvas/public/registries.ts +++ b/x-pack/plugins/canvas/public/registries.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore untyped module +// @ts-expect-error untyped module import { addRegistries, register } from '@kbn/interpreter/common'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { elementsRegistry } from './lib/elements_registry'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { templatesRegistry } from './lib/templates_registry'; import { tagsRegistry } from './lib/tags_registry'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { transitionsRegistry } from './lib/transitions_registry'; import { @@ -20,7 +20,7 @@ import { modelRegistry, transformRegistry, viewRegistry, - // @ts-ignore untyped local + // @ts-expect-error untyped local } from './expression_types'; import { SetupRegistries } from './plugin_api'; diff --git a/x-pack/plugins/canvas/public/state/actions/embeddable.ts b/x-pack/plugins/canvas/public/state/actions/embeddable.ts index a153cb7f4354d..874d390277320 100644 --- a/x-pack/plugins/canvas/public/state/actions/embeddable.ts +++ b/x-pack/plugins/canvas/public/state/actions/embeddable.ts @@ -7,7 +7,7 @@ import { Dispatch } from 'redux'; import { createAction } from 'redux-actions'; import { createThunk } from '../../lib/create_thunk'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { fetchRenderable } from './elements'; import { State } from '../../../types'; diff --git a/x-pack/plugins/canvas/public/state/actions/workpad.ts b/x-pack/plugins/canvas/public/state/actions/workpad.ts index 47df38838f890..419832e404594 100644 --- a/x-pack/plugins/canvas/public/state/actions/workpad.ts +++ b/x-pack/plugins/canvas/public/state/actions/workpad.ts @@ -8,7 +8,7 @@ import { createAction } from 'redux-actions'; import { without, includes } from 'lodash'; import { createThunk } from '../../lib/create_thunk'; import { getWorkpadColors } from '../selectors/workpad'; -// @ts-ignore +// @ts-expect-error import { fetchAllRenderables } from './elements'; import { CanvasWorkpad } from '../../../types'; diff --git a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts index 11ebdcdc51d4d..bb7b26919ef20 100644 --- a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts +++ b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts @@ -10,7 +10,7 @@ jest.mock('../../../lib/router_provider'); import { workpadAutoplay } from '../workpad_autoplay'; import { setAutoplayInterval } from '../../../lib/app_state'; import { createTimeInterval } from '../../../lib/time_interval'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { routerProvider } from '../../../lib/router_provider'; const next = jest.fn(); diff --git a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts index f90f570bc6ebf..bf69a862d5c30 100644 --- a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts +++ b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts @@ -9,7 +9,6 @@ jest.mock('../../../lib/app_state'); import { workpadRefresh } from '../workpad_refresh'; import { inFlightComplete } from '../../actions/resolved_args'; -// @ts-ignore untyped local import { setRefreshInterval } from '../../actions/workpad'; import { setRefreshInterval as setAppStateRefreshInterval } from '../../../lib/app_state'; diff --git a/x-pack/plugins/canvas/public/state/middleware/in_flight.ts b/x-pack/plugins/canvas/public/state/middleware/in_flight.ts index 7ad6f8aee15ed..028b9f214133f 100644 --- a/x-pack/plugins/canvas/public/state/middleware/in_flight.ts +++ b/x-pack/plugins/canvas/public/state/middleware/in_flight.ts @@ -9,7 +9,7 @@ import { loadingIndicator as defaultLoadingIndicator, LoadingIndicatorInterface, } from '../../lib/loading_indicator'; -// @ts-ignore +// @ts-expect-error import { convert } from '../../lib/modify_path'; interface InFlightMiddlewareOptions { diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts b/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts index dd484521c1b35..f77a1e1ba3295 100644 --- a/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts +++ b/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts @@ -9,9 +9,9 @@ import { State } from '../../../types'; import { getFullscreen } from '../selectors/app'; import { getInFlight } from '../selectors/resolved_args'; import { getWorkpad, getPages, getSelectedPageIndex, getAutoplay } from '../selectors/workpad'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { appUnload } from '../actions/app'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { routerProvider } from '../../lib/router_provider'; import { setAutoplayInterval } from '../../lib/app_state'; import { createTimeInterval } from '../../lib/time_interval'; diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts b/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts index 96a84b22cfccc..4a17ffb464532 100644 --- a/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts +++ b/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts @@ -6,11 +6,10 @@ import { Middleware } from 'redux'; import { State } from '../../../types'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { fetchAllRenderables } from '../actions/elements'; -// @ts-ignore Untyped Local import { setRefreshInterval } from '../actions/workpad'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { appUnload } from '../actions/app'; import { inFlightComplete } from '../actions/resolved_args'; import { getInFlight } from '../selectors/resolved_args'; diff --git a/x-pack/plugins/canvas/public/state/reducers/embeddable.ts b/x-pack/plugins/canvas/public/state/reducers/embeddable.ts index 8642239fa10d3..fdeb5087f26e1 100644 --- a/x-pack/plugins/canvas/public/state/reducers/embeddable.ts +++ b/x-pack/plugins/canvas/public/state/reducers/embeddable.ts @@ -13,7 +13,7 @@ import { UpdateEmbeddableExpressionPayload, } from '../actions/embeddable'; -// @ts-ignore untyped local +// @ts-expect-error untyped local import { assignNodeProperties } from './elements'; export const embeddableReducer = handleActions< diff --git a/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts b/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts index 9e2036e02f2b9..766e27d95da9b 100644 --- a/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts +++ b/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts @@ -5,9 +5,9 @@ */ import { get } from 'lodash'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import * as argHelper from '../../lib/resolved_arg'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { prepend } from '../../lib/modify_path'; import { State } from '../../../types'; diff --git a/x-pack/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/plugins/canvas/public/state/selectors/workpad.ts index 55bf2a7ea31f7..0f4953ff56d98 100644 --- a/x-pack/plugins/canvas/public/state/selectors/workpad.ts +++ b/x-pack/plugins/canvas/public/state/selectors/workpad.ts @@ -5,9 +5,9 @@ */ import { get, omit } from 'lodash'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { safeElementFromExpression, fromExpression } from '@kbn/interpreter/common'; -// @ts-ignore Untyped Local +// @ts-expect-error untyped local import { append } from '../../lib/modify_path'; import { getAssets } from './assets'; import { State, CanvasWorkpad, CanvasPage, CanvasElement, ResolvedArgType } from '../../../types'; diff --git a/x-pack/plugins/canvas/public/store.ts b/x-pack/plugins/canvas/public/store.ts index 81edec6ec539c..ef93a34296da2 100644 --- a/x-pack/plugins/canvas/public/store.ts +++ b/x-pack/plugins/canvas/public/store.ts @@ -9,9 +9,9 @@ import { destroyStore as destroy, getStore, cloneStore, - // @ts-ignore Untyped local + // @ts-expect-error untyped local } from './state/store'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { getInitialState } from './state/initial_state'; import { CoreSetup } from '../../../../src/core/public'; diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts index 8f3ced13895f6..7a9830124e305 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts @@ -8,7 +8,7 @@ import { mapValues, keys } from 'lodash'; import { schema } from '@kbn/config-schema'; import { API_ROUTE } from '../../../common/lib'; import { catchErrorHandler } from '../catch_error_handler'; -// @ts-ignore unconverted lib +// @ts-expect-error unconverted lib import { normalizeType } from '../../lib/normalize_type'; import { RouteInitializerDeps } from '..'; diff --git a/x-pack/plugins/canvas/server/routes/shareables/download.ts b/x-pack/plugins/canvas/server/routes/shareables/download.ts index 3f331c1635e16..0c86f8472c791 100644 --- a/x-pack/plugins/canvas/server/routes/shareables/download.ts +++ b/x-pack/plugins/canvas/server/routes/shareables/download.ts @@ -21,7 +21,6 @@ export function initializeDownloadShareableWorkpadRoute(deps: RouteInitializerDe // // The option setting is not for typical use. We're using it here to avoid // problems in Cloud environments. See elastic/kibana#47405. - // @ts-ignore No type for inert Hapi handler // const file = handler.file(SHAREABLE_RUNTIME_FILE, { confine: false }); const file = readFileSync(SHAREABLE_RUNTIME_FILE); return response.ok({ diff --git a/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts b/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts index f58111000859a..f5dcf59dcf45b 100644 --- a/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts +++ b/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts @@ -6,7 +6,6 @@ import { SampleDataRegistrySetup } from 'src/plugins/home/server'; import { CANVAS as label } from '../../i18n'; -// @ts-ignore Untyped local import { ecommerceSavedObjects, flightsSavedObjects, webLogsSavedObjects } from './index'; export function loadSampleData( @@ -16,9 +15,9 @@ export function loadSampleData( const now = new Date(); const nowTimestamp = now.toISOString(); - // @ts-ignore: Untyped local + // @ts-expect-error: untyped local function updateCanvasWorkpadTimestamps(savedObjects) { - // @ts-ignore: Untyped local + // @ts-expect-error: untyped local return savedObjects.map((savedObject) => { if (savedObject.type === 'canvas-workpad') { savedObject.attributes['@timestamp'] = nowTimestamp; diff --git a/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx b/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx index d99c9b190f83d..4b3aa8dc2fb6e 100644 --- a/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx @@ -15,7 +15,7 @@ jest.mock('../../supported_renderers'); describe('Canvas Shareable Workpad API', () => { // Mock the AJAX load of the workpad. beforeEach(function () { - // @ts-ignore Applying a global in Jest is alright. + // @ts-expect-error Applying a global in Jest is alright. global.fetch = jest.fn().mockImplementation(() => { const p = new Promise((resolve, _reject) => { resolve({ diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx index 7b5a5080ae790..899edee7f0481 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx @@ -7,7 +7,7 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; import { ExampleContext } from '../../test/context_example'; -// @ts-ignore +// @ts-expect-error import { image } from '../../../canvas_plugin_src/renderers/image'; import { sharedWorkpads } from '../../test'; import { RenderedElement, RenderedElementComponent } from '../rendered_element'; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/autoplay_settings.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/autoplay_settings.tsx index 1650cbad3a237..4c7c65511698d 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/autoplay_settings.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/autoplay_settings.tsx @@ -12,7 +12,6 @@ import { setAutoplayIntervalAction, } from '../../../context'; import { createTimeInterval } from '../../../../public/lib/time_interval'; -// @ts-ignore Untyped local import { CustomInterval } from '../../../../public/components/workpad_header/view_menu/custom_interval'; export type onSetAutoplayFn = (autoplay: boolean) => void; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx index c4a009db3a376..5741f5f2d698c 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx @@ -5,11 +5,11 @@ */ import React, { FC, PureComponent } from 'react'; -// @ts-ignore Untyped library +// @ts-expect-error untyped library import Style from 'style-it'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { Positionable } from '../../public/components/positionable/positionable'; -// @ts-ignore Untyped local +// @ts-expect-error untyped local import { elementToShape } from '../../public/components/workpad_page/utils'; import { CanvasRenderedElement } from '../types'; import { CanvasShareableContext, useCanvasShareableState } from '../context'; diff --git a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js index 6238aaf5c2fe4..340d1fb418b4c 100644 --- a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js +++ b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// This is a JS file because the renderers are not strongly-typed yet. Tagging for -// visibility. -// @ts-ignore Untyped local - import { debug } from '../canvas_plugin_src/renderers/debug'; import { error } from '../canvas_plugin_src/renderers/error'; import { image } from '../canvas_plugin_src/renderers/image'; diff --git a/x-pack/plugins/canvas/shareable_runtime/types.ts b/x-pack/plugins/canvas/shareable_runtime/types.ts index 191c0405d2e2d..040062346c74f 100644 --- a/x-pack/plugins/canvas/shareable_runtime/types.ts +++ b/x-pack/plugins/canvas/shareable_runtime/types.ts @@ -5,7 +5,7 @@ */ import { RefObject } from 'react'; -// @ts-ignore Unlinked Webpack Type +// @ts-expect-error Unlinked Webpack Type import ContainerStyle from 'types/interpreter'; import { SavedObject, SavedObjectAttributes } from 'src/core/public'; From b2d3833313c7b4ccc33fa57ebdd4017af394a10f Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Tue, 23 Jun 2020 15:14:40 -0400 Subject: [PATCH 16/36] [Monitoring] Love for APM (#69052) * Fix broken colors for APM * Use default derivative * Fix UI issues with APM * Add new charts * Fix tests * Use EUI color palette * Remove old translations * PR feedback * Fix tests * Fix up overview page Co-authored-by: Elastic Machine --- .../components/apm/instance/instance.js | 16 +- .../components/apm/instances/instances.js | 5 +- .../public/components/apm/overview/index.js | 4 +- .../public/components/chart/get_color.js | 4 + .../monitoring/public/services/breadcrumbs.js | 2 + .../__snapshots__/metrics.test.js.snap | 506 ++++- .../server/lib/metrics/apm/classes.js | 7 +- .../server/lib/metrics/apm/metrics.js | 139 ++ .../server/routes/api/v1/apm/instance.js | 2 +- .../routes/api/v1/apm/metric_set_instance.js | 23 + .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../apis/monitoring/apm/fixtures/cluster.json | 1719 ++++++++++------- .../monitoring/apm/fixtures/instance.json | 1607 ++++++++------- 14 files changed, 2553 insertions(+), 1483 deletions(-) diff --git a/x-pack/plugins/monitoring/public/components/apm/instance/instance.js b/x-pack/plugins/monitoring/public/components/apm/instance/instance.js index cb7187a8c0753..396d2258edd0c 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instance/instance.js +++ b/x-pack/plugins/monitoring/public/components/apm/instance/instance.js @@ -21,18 +21,22 @@ import { FormattedMessage } from '@kbn/i18n/react'; export function ApmServerInstance({ summary, metrics, ...props }) { const seriesToShow = [ + metrics.apm_requests, metrics.apm_responses_valid, + metrics.apm_responses_errors, + metrics.apm_acm_request_count, + + metrics.apm_acm_response, + metrics.apm_acm_response_errors, metrics.apm_output_events_rate_success, metrics.apm_output_events_rate_failure, - metrics.apm_requests, metrics.apm_transformations, - metrics.apm_cpu, - metrics.apm_memory, + metrics.apm_memory, metrics.apm_os_load, ]; @@ -56,8 +60,10 @@ export function ApmServerInstance({ summary, metrics, ...props }) { - - + + + + {charts} diff --git a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js index 9d0d2c5aefa56..7754af1be8588 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js +++ b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js @@ -15,6 +15,7 @@ import { EuiPageContent, EuiSpacer, EuiScreenReaderOnly, + EuiPanel, } from '@elastic/eui'; import { Status } from './status'; import { formatMetric } from '../../../lib/format_number'; @@ -154,7 +155,9 @@ export function ApmServerInstances({ apms, setupMode }) { - + + + {setupModeCallout} - + + + {charts} diff --git a/x-pack/plugins/monitoring/public/components/chart/get_color.js b/x-pack/plugins/monitoring/public/components/chart/get_color.js index e4a5777bb6efe..868b914a16c8a 100644 --- a/x-pack/plugins/monitoring/public/components/chart/get_color.js +++ b/x-pack/plugins/monitoring/public/components/chart/get_color.js @@ -13,10 +13,14 @@ * @param {Integer} index: index of the chart series, 0-3 * @returns {String} Hex color to use for chart series at the given index */ +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + export function getColor(app, index) { let seriesColors; if (app === 'elasticsearch') { seriesColors = ['#3ebeb0', '#3b73ac', '#f08656', '#6c478f']; + } else if (app === 'apm') { + seriesColors = euiPaletteColorBlind(); } else { // for kibana, and fallback (e.g., Logstash and Beats) seriesColors = ['#e8488b', '#3b73ac', '#3cab63', '#6c478f']; diff --git a/x-pack/plugins/monitoring/public/services/breadcrumbs.js b/x-pack/plugins/monitoring/public/services/breadcrumbs.js index 44422a42f5f0a..63bac2975dc20 100644 --- a/x-pack/plugins/monitoring/public/services/breadcrumbs.js +++ b/x-pack/plugins/monitoring/public/services/breadcrumbs.js @@ -76,6 +76,7 @@ function getKibanaBreadcrumbs(mainInstance) { }) ) ); + breadcrumbs.push(createCrumb(null, mainInstance.instance)); } else { // don't link to Overview when we're possibly on Overview or its sibling tabs breadcrumbs.push(createCrumb(null, 'Kibana')); @@ -160,6 +161,7 @@ function getApmBreadcrumbs(mainInstance) { }) ) ); + breadcrumbs.push(createCrumb(null, mainInstance.instance)); } else { // don't link to Overview when we're possibly on Overview or its sibling tabs breadcrumbs.push(createCrumb(null, apmLabel)); diff --git a/x-pack/plugins/monitoring/server/lib/metrics/__test__/__snapshots__/metrics.test.js.snap b/x-pack/plugins/monitoring/server/lib/metrics/__test__/__snapshots__/metrics.test.js.snap index 1cc442cb15993..74916fb0a0789 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/__test__/__snapshots__/metrics.test.js.snap +++ b/x-pack/plugins/monitoring/server/lib/metrics/__test__/__snapshots__/metrics.test.js.snap @@ -2,6 +2,416 @@ exports[`Metrics should export metric objects that match a snapshot 1`] = ` Object { + "apm_acm_request_count": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.request.count", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "HTTP Requests received by agent configuration managemen", + "field": "beats_stats.metrics.apm-server.acm.request.count", + "format": "0,0.[00]", + "label": "Count", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Requests Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_count": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.count", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "HTTP requests responded to by APM Server", + "field": "beats_stats.metrics.apm-server.acm.response.count", + "format": "0,0.[00]", + "label": "Count", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response Count Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_errors_count": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.errors.count", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "HTTP errors count", + "field": "beats_stats.metrics.apm-server.acm.response.errors.count", + "format": "0,0.[00]", + "label": "Error Count", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response Error Count Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_errors_forbidden": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.errors.forbidden", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "Forbidden HTTP requests rejected count", + "field": "beats_stats.metrics.apm-server.acm.response.errors.forbidden", + "format": "0,0.[00]", + "label": "Count", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response Errors Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_errors_invalidquery": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.errors.invalidquery", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "Invalid HTTP query", + "field": "beats_stats.metrics.apm-server.acm.response.errors.invalidquery", + "format": "0,0.[00]", + "label": "Invalid Query", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response Invalid Query Errors Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_errors_method": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.errors.method", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "HTTP requests rejected due to incorrect HTTP method", + "field": "beats_stats.metrics.apm-server.acm.response.errors.method", + "format": "0,0.[00]", + "label": "Method", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response Method Errors Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_errors_unauthorized": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.errors.unauthorized", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "Unauthorized HTTP requests rejected count", + "field": "beats_stats.metrics.apm-server.acm.response.errors.unauthorized", + "format": "0,0.[00]", + "label": "Unauthorized", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response Unauthorized Errors Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_errors_unavailable": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.errors.unavailable", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "Unavailable HTTP response count. Possible misconfiguration or unsupported version of Kibana", + "field": "beats_stats.metrics.apm-server.acm.response.errors.unavailable", + "format": "0,0.[00]", + "label": "Unavailable", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response Unavailable Errors Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_valid_notmodified": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.valid.notmodified", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "304 Not modified response count", + "field": "beats_stats.metrics.apm-server.acm.response.valid.notmodified", + "format": "0,0.[00]", + "label": "Not Modified", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response Not Modified Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, + "apm_acm_response_valid_ok": ApmEventsRateClusterMetric { + "aggs": Object { + "beats_uuids": Object { + "aggs": Object { + "event_rate_per_beat": Object { + "max": Object { + "field": "beats_stats.metrics.apm-server.acm.response.valid.ok", + }, + }, + }, + "terms": Object { + "field": "beats_stats.beat.uuid", + "size": 10000, + }, + }, + "event_rate": Object { + "sum_bucket": Object { + "buckets_path": "beats_uuids>event_rate_per_beat", + "gap_policy": "skip", + }, + }, + "metric_deriv": Object { + "derivative": Object { + "buckets_path": "event_rate", + "gap_policy": "skip", + "unit": "1s", + }, + }, + }, + "app": "apm", + "derivative": true, + "description": "200 OK response count", + "field": "beats_stats.metrics.apm-server.acm.response.valid.ok", + "format": "0,0.[00]", + "label": "OK", + "metricAgg": "max", + "timestampField": "beats_stats.timestamp", + "title": "Response OK Count Agent Configuration Management", + "units": "/s", + "uuidField": "cluster_uuid", + }, "apm_cpu_total": ApmCpuUtilizationMetric { "app": "apm", "calculation": [Function], @@ -80,7 +490,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -93,7 +503,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Output Acked Events Rate", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_output_events_active": ApmEventsRateClusterMetric { @@ -121,7 +531,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -134,7 +544,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Output Active Events Rate", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_output_events_dropped": ApmEventsRateClusterMetric { @@ -162,7 +572,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -175,7 +585,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Output Dropped Events Rate", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_output_events_failed": ApmEventsRateClusterMetric { @@ -203,7 +613,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -216,7 +626,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Output Failed Events Rate", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_output_events_total": ApmEventsRateClusterMetric { @@ -244,7 +654,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -257,7 +667,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Output Events Rate", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_processor_error_transformations": ApmEventsRateClusterMetric { @@ -285,7 +695,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -298,7 +708,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Transformations", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_processor_metric_transformations": ApmEventsRateClusterMetric { @@ -326,7 +736,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -339,7 +749,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Transformations", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_processor_span_transformations": ApmEventsRateClusterMetric { @@ -367,7 +777,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -380,7 +790,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Transformations", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_processor_transaction_transformations": ApmEventsRateClusterMetric { @@ -408,7 +818,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -421,7 +831,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Processed Events", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_requests": ApmEventsRateClusterMetric { @@ -449,7 +859,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -462,7 +872,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Request Count Intake API", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_count": ApmEventsRateClusterMetric { @@ -490,7 +900,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -503,7 +913,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Response Count Intake API", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_closed": ApmEventsRateClusterMetric { @@ -531,7 +941,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -544,7 +954,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Closed", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_concurrency": ApmEventsRateClusterMetric { @@ -572,7 +982,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -585,7 +995,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Concurrency", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_decode": ApmEventsRateClusterMetric { @@ -613,7 +1023,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -626,7 +1036,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Decode", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_forbidden": ApmEventsRateClusterMetric { @@ -654,7 +1064,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -667,7 +1077,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Forbidden", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_internal": ApmEventsRateClusterMetric { @@ -695,7 +1105,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -708,7 +1118,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Internal", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_method": ApmEventsRateClusterMetric { @@ -736,7 +1146,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -749,7 +1159,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Method", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_queue": ApmEventsRateClusterMetric { @@ -777,7 +1187,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -790,7 +1200,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Queue", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_ratelimit": ApmEventsRateClusterMetric { @@ -818,7 +1228,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -831,7 +1241,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Rate limit", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_toolarge": ApmEventsRateClusterMetric { @@ -859,7 +1269,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -872,7 +1282,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Response Errors Intake API", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_unauthorized": ApmEventsRateClusterMetric { @@ -900,7 +1310,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -913,7 +1323,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Unauthorized", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_errors_validate": ApmEventsRateClusterMetric { @@ -941,7 +1351,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -954,7 +1364,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Validate", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_valid_accepted": ApmEventsRateClusterMetric { @@ -982,7 +1392,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -995,7 +1405,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Accepted", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_responses_valid_ok": ApmEventsRateClusterMetric { @@ -1023,7 +1433,7 @@ Object { "derivative": Object { "buckets_path": "event_rate", "gap_policy": "skip", - "unit": "1m", + "unit": "1s", }, }, }, @@ -1036,7 +1446,7 @@ Object { "metricAgg": "max", "timestampField": "beats_stats.timestamp", "title": "Ok", - "units": "/m", + "units": "/s", "uuidField": "cluster_uuid", }, "apm_system_os_load_1": ApmMetric { diff --git a/x-pack/plugins/monitoring/server/lib/metrics/apm/classes.js b/x-pack/plugins/monitoring/server/lib/metrics/apm/classes.js index fc8a45669bd94..840f5d5cb239e 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/apm/classes.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/apm/classes.js @@ -7,6 +7,7 @@ import { ClusterMetric, Metric } from '../classes'; import { SMALL_FLOAT, LARGE_FLOAT } from '../../../../common/formatting'; import { i18n } from '@kbn/i18n'; +import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../common/constants'; export class ApmClusterMetric extends ClusterMetric { constructor(opts) { @@ -76,8 +77,8 @@ export class ApmEventsRateClusterMetric extends ApmClusterMetric { derivative: true, format: LARGE_FLOAT, metricAgg: 'max', - units: i18n.translate('xpack.monitoring.metrics.apm.perMinuteUnitLabel', { - defaultMessage: '/m', + units: i18n.translate('xpack.monitoring.metrics.apm.perSecondUnitLabel', { + defaultMessage: '/s', }), }); @@ -105,7 +106,7 @@ export class ApmEventsRateClusterMetric extends ApmClusterMetric { derivative: { buckets_path: 'event_rate', gap_policy: 'skip', - unit: '1m', + unit: NORMALIZED_DERIVATIVE_UNIT, }, }, }; diff --git a/x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.js index db2eae591f359..1d063a57bbb5b 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.js @@ -449,4 +449,143 @@ export const metrics = { } ), }), + apm_acm_response_count: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.count', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.countTitle', { + defaultMessage: 'Response Count Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.countLabel', { + defaultMessage: 'Count', + }), + description: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.countDescription', { + defaultMessage: 'HTTP requests responded to by APM Server', + }), + }), + apm_acm_response_errors_count: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.errors.count', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errorCountTitle', { + defaultMessage: 'Response Error Count Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errorCountLabel', { + defaultMessage: 'Error Count', + }), + description: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errorCountDescription', { + defaultMessage: 'HTTP errors count', + }), + }), + apm_acm_response_valid_ok: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.valid.ok', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.validOkTitle', { + defaultMessage: 'Response OK Count Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.validOkLabel', { + defaultMessage: 'OK', + }), + description: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.validOkDescription', { + defaultMessage: '200 OK response count', + }), + }), + apm_acm_response_valid_notmodified: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.valid.notmodified', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.validNotModifiedTitle', { + defaultMessage: 'Response Not Modified Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.validNotModifiedLabel', { + defaultMessage: 'Not Modified', + }), + description: i18n.translate( + 'xpack.monitoring.metrics.apm.acmResponse.validNotModifiedDescription', + { + defaultMessage: '304 Not modified response count', + } + ), + }), + apm_acm_response_errors_forbidden: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.errors.forbidden', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.forbiddenTitle', { + defaultMessage: 'Response Errors Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.forbiddenLabel', { + defaultMessage: 'Count', + }), + description: i18n.translate( + 'xpack.monitoring.metrics.apm.acmResponse.errors.forbiddenDescription', + { + defaultMessage: 'Forbidden HTTP requests rejected count', + } + ), + }), + apm_acm_response_errors_unauthorized: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.errors.unauthorized', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.unauthorizedTitle', { + defaultMessage: 'Response Unauthorized Errors Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.unauthorizedLabel', { + defaultMessage: 'Unauthorized', + }), + description: i18n.translate( + 'xpack.monitoring.metrics.apm.acmResponse.errors.unauthorizedDescription', + { + defaultMessage: 'Unauthorized HTTP requests rejected count', + } + ), + }), + apm_acm_response_errors_unavailable: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.errors.unavailable', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.unavailableTitle', { + defaultMessage: 'Response Unavailable Errors Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.unavailableLabel', { + defaultMessage: 'Unavailable', + }), + description: i18n.translate( + 'xpack.monitoring.metrics.apm.acmResponse.errors.unavailableDescription', + { + defaultMessage: + 'Unavailable HTTP response count. Possible misconfiguration or unsupported version of Kibana', + } + ), + }), + apm_acm_response_errors_method: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.errors.method', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.methodTitle', { + defaultMessage: 'Response Method Errors Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.methodLabel', { + defaultMessage: 'Method', + }), + description: i18n.translate( + 'xpack.monitoring.metrics.apm.acmResponse.errors.methodDescription', + { + defaultMessage: 'HTTP requests rejected due to incorrect HTTP method', + } + ), + }), + apm_acm_response_errors_invalidquery: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.response.errors.invalidquery', + title: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.invalidqueryTitle', { + defaultMessage: 'Response Invalid Query Errors Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmResponse.errors.invalidqueryLabel', { + defaultMessage: 'Invalid Query', + }), + description: i18n.translate( + 'xpack.monitoring.metrics.apm.acmResponse.errors.invalidqueryDescription', + { + defaultMessage: 'Invalid HTTP query', + } + ), + }), + apm_acm_request_count: new ApmEventsRateClusterMetric({ + field: 'beats_stats.metrics.apm-server.acm.request.count', + title: i18n.translate('xpack.monitoring.metrics.apm.acmRequest.countTitle', { + defaultMessage: 'Requests Agent Configuration Management', + }), + label: i18n.translate('xpack.monitoring.metrics.apm.acmRequest.countTitleLabel', { + defaultMessage: 'Count', + }), + description: i18n.translate('xpack.monitoring.metrics.apm.acmRequest.countTitleDescription', { + defaultMessage: 'HTTP Requests received by agent configuration managemen', + }), + }), }; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js index 0ff9e834b924c..16921e998f296 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { getMetrics } from '../../../../lib/details/get_metrics'; -import { metricSet } from './metric_set_overview'; +import { metricSet } from './metric_set_instance'; import { handleError } from '../../../../lib/errors'; import { getApmInfo } from '../../../../lib/apm'; import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_instance.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_instance.js index 9ce96d386ae2f..5ace433c295da 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_instance.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/metric_set_instance.js @@ -59,4 +59,27 @@ export const metricSet = [ ], name: 'apm_transformations', }, + { + keys: [ + 'apm_acm_response_count', + 'apm_acm_response_errors_count', + 'apm_acm_response_valid_ok', + 'apm_acm_response_valid_notmodified', + ], + name: 'apm_acm_response', + }, + { + keys: [ + 'apm_acm_response_errors_forbidden', + 'apm_acm_response_errors_unauthorized', + 'apm_acm_response_errors_unavailable', + 'apm_acm_response_errors_method', + 'apm_acm_response_errors_invalidquery', + ], + name: 'apm_acm_response_errors', + }, + { + keys: ['apm_acm_request_count'], + name: 'apm_acm_request_count', + }, ]; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0567ee675ee75..d6999c3f12cfa 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11717,7 +11717,6 @@ "xpack.monitoring.metrics.apm.outputFailedEventsRate.failedDescription": "アウトプットにより処理されたイベントです (再試行を含む)", "xpack.monitoring.metrics.apm.outputFailedEventsRate.failedLabel": "失敗", "xpack.monitoring.metrics.apm.outputFailedEventsRateTitle": "アウトプットイベント失敗率", - "xpack.monitoring.metrics.apm.perMinuteUnitLabel": "/m", "xpack.monitoring.metrics.apm.processedEvents.transactionDescription": "処理されたトランザクションイベントです", "xpack.monitoring.metrics.apm.processedEvents.transactionLabel": "トランザクション", "xpack.monitoring.metrics.apm.processedEventsTitle": "処理済みのイベント", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 86f2c44c809da..985d85338d0a1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11722,7 +11722,6 @@ "xpack.monitoring.metrics.apm.outputFailedEventsRate.failedDescription": "输出处理的事件(包括重试)", "xpack.monitoring.metrics.apm.outputFailedEventsRate.failedLabel": "失败", "xpack.monitoring.metrics.apm.outputFailedEventsRateTitle": "输出失败事件速率", - "xpack.monitoring.metrics.apm.perMinuteUnitLabel": "/分钟", "xpack.monitoring.metrics.apm.processedEvents.transactionDescription": "已处理事务事件", "xpack.monitoring.metrics.apm.processedEvents.transactionLabel": "事务", "xpack.monitoring.metrics.apm.processedEventsTitle": "已处理事件", diff --git a/x-pack/test/api_integration/apis/monitoring/apm/fixtures/cluster.json b/x-pack/test/api_integration/apis/monitoring/apm/fixtures/cluster.json index aa9e4cc125a90..e54a1c2210d48 100644 --- a/x-pack/test/api_integration/apis/monitoring/apm/fixtures/cluster.json +++ b/x-pack/test/api_integration/apis/monitoring/apm/fixtures/cluster.json @@ -9,727 +9,1046 @@ "timeOfLastEvent": "2018-08-31T13:59:21.201Z" }, "metrics": { - "apm_cpu": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + "apm_cpu": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.beat.cpu.total.value", + "metricAgg": "max", + "label": "Total", + "title": "CPU Utilization", + "description": "Percentage of CPU time spent executing (user+kernel mode) for the APM process", + "units": "%", + "format": "0.[00]", + "hasCalculation": true, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0.1 + ], + [ + 1535723940000, + 0.26666666666666666 + ] + ] + } + ], + "apm_os_load": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.system.load.1", + "metricAgg": "max", + "label": "1m", + "title": "System Load", + "description": "Load average over the last 1 minute", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1535723880000, + 1.37 + ], + [ + 1535723910000, + 1.01 + ], + [ + 1535723940000, + 0.61 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.beat.cpu.total.value", - "metricAgg": "max", - "label": "Total", - "title": "CPU Utilization", - "description": "Percentage of CPU time spent executing (user+kernel mode) for the APM process", - "units": "%", - "format": "0.[00]", - "hasCalculation": true, - "isDerivative": true + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.system.load.5", + "metricAgg": "max", + "label": "5m", + "title": "System Load", + "description": "Load average over the last 5 minutes", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1535723880000, + 1.72 + ], + [ + 1535723910000, + 1.6 + ], + [ + 1535723940000, + 1.45 + ] + ] }, - "data": [ - [1535723880000, null], - [1535723910000, 0.1], - [1535723940000, 0.26666666666666666] - ] - }], - "apm_os_load": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.system.load.15", + "metricAgg": "max", + "label": "15m", + "title": "System Load", + "description": "Load average over the last 15 minutes", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1535723880000, + 2.5 + ], + [ + 1535723910000, + 2.43 + ], + [ + 1535723940000, + 2.35 + ] + ] + } + ], + "apm_memory": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.beat.memstats.memory_alloc", + "metricAgg": "max", + "label": "Allocated Memory", + "title": "Memory", + "description": "Allocated memory", + "units": "B", + "format": "0,0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1535723880000, + 4660952 + ], + [ + 1535723910000, + 3888048 + ], + [ + 1535723940000, + 3445920 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.system.load.1", - "metricAgg": "max", - "label": "1m", - "title": "System Load", - "description": "Load average over the last 1 minute", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.beat.memstats.rss", + "metricAgg": "max", + "label": "Process Total", + "title": "Memory", + "description": "Resident set size of memory reserved by the APM service from the OS", + "units": "B", + "format": "0,0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1535723880000, + 10866688 + ], + [ + 1535723910000, + 11456512 + ], + [ + 1535723940000, + 12095488 + ] + ] }, - "data": [ - [1535723880000, 1.37], - [1535723910000, 1.01], - [1535723940000, 0.61] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.beat.memstats.gc_next", + "metricAgg": "max", + "label": "GC Next", + "title": "Memory", + "description": "Limit of allocated memory at which garbage collection will occur", + "units": "B", + "format": "0,0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1535723880000, + 5212816 + ], + [ + 1535723910000, + 4996912 + ], + [ + 1535723940000, + 4886176 + ] + ] + } + ], + "apm_output_events_rate_success": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.total", + "metricAgg": "max", + "label": "Total", + "title": "Output Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0.3 + ], + [ + 1535723940000, + 0.2 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.system.load.5", - "metricAgg": "max", - "label": "5m", - "title": "System Load", - "description": "Load average over the last 5 minutes", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.active", + "metricAgg": "max", + "label": "Active", + "title": "Output Active Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + null + ], + [ + 1535723940000, + 0 + ] + ] }, - "data": [ - [1535723880000, 1.72], - [1535723910000, 1.6], - [1535723940000, 1.45] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.acked", + "metricAgg": "max", + "label": "Acked", + "title": "Output Acked Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0.3 + ], + [ + 1535723940000, + 0.2 + ] + ] + } + ], + "apm_output_events_rate_failure": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.failed", + "metricAgg": "max", + "label": "Failed", + "title": "Output Failed Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.system.load.15", - "metricAgg": "max", - "label": "15m", - "title": "System Load", - "description": "Load average over the last 15 minutes", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.dropped", + "metricAgg": "max", + "label": "Dropped", + "title": "Output Dropped Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] + } + ], + "apm_responses_valid": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.count", + "metricAgg": "max", + "label": "Total", + "title": "Response Count Intake API", + "description": "HTTP Requests responded to by server", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 1.4 + ], + [ + 1535723940000, + 1.4 + ] + ] }, - "data": [ - [1535723880000, 2.5], - [1535723910000, 2.43], - [1535723940000, 2.35] - ] - }], - "apm_memory": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.valid.ok", + "metricAgg": "max", + "label": "Ok", + "title": "Ok", + "description": "200 OK response count", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 1.3333333333333333 + ], + [ + 1535723940000, + 1.3666666666666667 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.beat.memstats.memory_alloc", - "metricAgg": "max", - "label": "Allocated Memory", - "title": "Memory", - "description": "Allocated memory", - "units": "B", - "format": "0,0.0 b", - "hasCalculation": false, - "isDerivative": false + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.valid.accepted", + "metricAgg": "max", + "label": "Accepted", + "title": "Accepted", + "description": "HTTP Requests successfully reporting new events", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0.06666666666666667 + ], + [ + 1535723940000, + 0.03333333333333333 + ] + ] + } + ], + "apm_responses_errors": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.toolarge", + "metricAgg": "max", + "label": "Too large", + "title": "Response Errors Intake API", + "description": "HTTP Requests rejected due to excessive payload size", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "data": [ - [1535723880000, 4660952], - [1535723910000, 3888048], - [1535723940000, 3445920] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.validate", + "metricAgg": "max", + "label": "Validate", + "title": "Validate", + "description": "HTTP Requests rejected due to payload validation error", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.beat.memstats.rss", - "metricAgg": "max", - "label": "Process Total", - "title": "Memory", - "description": "Resident set size of memory reserved by the APM service from the OS", - "units": "B", - "format": "0,0.0 b", - "hasCalculation": false, - "isDerivative": false + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.method", + "metricAgg": "max", + "label": "Method", + "title": "Method", + "description": "HTTP Requests rejected due to incorrect HTTP method", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "data": [ - [1535723880000, 10866688], - [1535723910000, 11456512], - [1535723940000, 12095488] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.unauthorized", + "metricAgg": "max", + "label": "Unauthorized", + "title": "Unauthorized", + "description": "HTTP Requests rejected due to invalid secret token", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.beat.memstats.gc_next", - "metricAgg": "max", - "label": "GC Next", - "title": "Memory", - "description": "Limit of allocated memory at which garbage collection will occur", - "units": "B", - "format": "0,0.0 b", - "hasCalculation": false, - "isDerivative": false + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.ratelimit", + "metricAgg": "max", + "label": "Rate limit", + "title": "Rate limit", + "description": "HTTP Requests rejected to due excessive rate limit", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "data": [ - [1535723880000, 5212816], - [1535723910000, 4996912], - [1535723940000, 4886176] - ] - }], - "apm_output_events_rate_success": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.queue", + "metricAgg": "max", + "label": "Queue", + "title": "Queue", + "description": "HTTP Requests rejected to due internal queue filling up", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.total", - "metricAgg": "max", - "label": "Total", - "title": "Output Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.decode", + "metricAgg": "max", + "label": "Decode", + "title": "Decode", + "description": "HTTP Requests rejected to due decoding errors - invalid json, incorrect data type for entity", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "data": [ - [1535723880000, null], - [1535723910000, 18], - [1535723940000, 12] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.forbidden", + "metricAgg": "max", + "label": "Forbidden", + "title": "Forbidden", + "description": "Forbidden HTTP Requests rejected - CORS violation, disabled enpoint", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.active", - "metricAgg": "max", - "label": "Active", - "title": "Output Active Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.concurrency", + "metricAgg": "max", + "label": "Concurrency", + "title": "Concurrency", + "description": "HTTP Requests rejected due to overall concurrency limit breach", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "data": [ - [1535723880000, null], - [1535723910000, null], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.closed", + "metricAgg": "max", + "label": "Closed", + "title": "Closed", + "description": "HTTP Requests rejected during server shutdown", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.acked", - "metricAgg": "max", - "label": "Acked", - "title": "Output Acked Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.internal", + "metricAgg": "max", + "label": "Internal", + "title": "Internal", + "description": "HTTP Requests rejected due to a miscellaneous internal error", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] + } + ], + "apm_requests": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.request.count", + "metricAgg": "max", + "label": "Requested", + "title": "Request Count Intake API", + "description": "HTTP Requests received by server", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 1.4 + ], + [ + 1535723940000, + 1.4 + ] + ] + } + ], + "apm_transformations": [ + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.processor.transaction.transformations", + "metricAgg": "max", + "label": "Transaction", + "title": "Processed Events", + "description": "Transaction events processed", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0.1 + ], + [ + 1535723940000, + 0.06666666666666667 + ] + ] }, - "data": [ - [1535723880000, null], - [1535723910000, 18], - [1535723940000, 12] - ] - }], - "apm_output_events_rate_failure": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.processor.span.transformations", + "metricAgg": "max", + "label": "Span", + "title": "Transformations", + "description": "Span events processed", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0.2 + ], + [ + 1535723940000, + 0.13333333333333333 + ] + ] }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.failed", - "metricAgg": "max", - "label": "Failed", - "title": "Output Failed Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.processor.error.transformations", + "metricAgg": "max", + "label": "Error", + "title": "Transformations", + "description": "Error events processed", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.dropped", - "metricAgg": "max", - "label": "Dropped", - "title": "Output Dropped Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }], - "apm_responses_valid": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.count", - "metricAgg": "max", - "label": "Total", - "title": "Response Count Intake API", - "description": "HTTP Requests responded to by server", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 84], - [1535723940000, 84] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.valid.ok", - "metricAgg": "max", - "label": "Ok", - "title": "Ok", - "description": "200 OK response count", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 80], - [1535723940000, 82] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.valid.accepted", - "metricAgg": "max", - "label": "Accepted", - "title": "Accepted", - "description": "HTTP Requests successfully reporting new events", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 4], - [1535723940000, 2] - ] - }], - "apm_responses_errors": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.toolarge", - "metricAgg": "max", - "label": "Too large", - "title": "Response Errors Intake API", - "description": "HTTP Requests rejected due to excessive payload size", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.validate", - "metricAgg": "max", - "label": "Validate", - "title": "Validate", - "description": "HTTP Requests rejected due to payload validation error", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.method", - "metricAgg": "max", - "label": "Method", - "title": "Method", - "description": "HTTP Requests rejected due to incorrect HTTP method", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.unauthorized", - "metricAgg": "max", - "label": "Unauthorized", - "title": "Unauthorized", - "description": "HTTP Requests rejected due to invalid secret token", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.ratelimit", - "metricAgg": "max", - "label": "Rate limit", - "title": "Rate limit", - "description": "HTTP Requests rejected to due excessive rate limit", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.queue", - "metricAgg": "max", - "label": "Queue", - "title": "Queue", - "description": "HTTP Requests rejected to due internal queue filling up", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.decode", - "metricAgg": "max", - "label": "Decode", - "title": "Decode", - "description": "HTTP Requests rejected to due decoding errors - invalid json, incorrect data type for entity", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.forbidden", - "metricAgg": "max", - "label": "Forbidden", - "title": "Forbidden", - "description": "Forbidden HTTP Requests rejected - CORS violation, disabled enpoint", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.concurrency", - "metricAgg": "max", - "label": "Concurrency", - "title": "Concurrency", - "description": "HTTP Requests rejected due to overall concurrency limit breach", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.closed", - "metricAgg": "max", - "label": "Closed", - "title": "Closed", - "description": "HTTP Requests rejected during server shutdown", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.internal", - "metricAgg": "max", - "label": "Internal", - "title": "Internal", - "description": "HTTP Requests rejected due to a miscellaneous internal error", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }], - "apm_requests": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.request.count", - "metricAgg": "max", - "label": "Requested", - "title": "Request Count Intake API", - "description": "HTTP Requests received by server", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 84], - [1535723940000, 84] - ] - }], - "apm_transformations": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.processor.transaction.transformations", - "metricAgg": "max", - "label": "Transaction", - "title": "Processed Events", - "description": "Transaction events processed", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 6], - [1535723940000, 4] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.processor.span.transformations", - "metricAgg": "max", - "label": "Span", - "title": "Transformations", - "description": "Span events processed", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 12], - [1535723940000, 8] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.processor.error.transformations", - "metricAgg": "max", - "label": "Error", - "title": "Transformations", - "description": "Error events processed", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.processor.metric.transformations", - "metricAgg": "max", - "label": "Metric", - "title": "Transformations", - "description": "Metric events processed", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }] + { + "bucket_size": "30 seconds", + "timeRange": { + "min": 1535720389104, + "max": 1535723989104 + }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.processor.metric.transformations", + "metricAgg": "max", + "label": "Metric", + "title": "Transformations", + "description": "Metric events processed", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1535723880000, + null + ], + [ + 1535723910000, + 0 + ], + [ + 1535723940000, + 0 + ] + ] + } + ] } } diff --git a/x-pack/test/api_integration/apis/monitoring/apm/fixtures/instance.json b/x-pack/test/api_integration/apis/monitoring/apm/fixtures/instance.json index abcbf7557234d..f1747507b71d5 100644 --- a/x-pack/test/api_integration/apis/monitoring/apm/fixtures/instance.json +++ b/x-pack/test/api_integration/apis/monitoring/apm/fixtures/instance.json @@ -1,727 +1,890 @@ { "metrics": { - "apm_cpu": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.beat.cpu.total.value", - "metricAgg": "max", - "label": "Total", - "title": "CPU Utilization", - "description": "Percentage of CPU time spent executing (user+kernel mode) for the APM process", - "units": "%", - "format": "0.[00]", - "hasCalculation": true, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0.13333333333333333], - [1535723940000, 0.3] - ] - }], - "apm_os_load": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.system.load.1", - "metricAgg": "max", - "label": "1m", - "title": "System Load", - "description": "Load average over the last 1 minute", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1535723880000, 1.37], - [1535723910000, 1.01], - [1535723940000, 0.61] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.system.load.5", - "metricAgg": "max", - "label": "5m", - "title": "System Load", - "description": "Load average over the last 5 minutes", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1535723880000, 1.72], - [1535723910000, 1.6], - [1535723940000, 1.45] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.system.load.15", - "metricAgg": "max", - "label": "15m", - "title": "System Load", - "description": "Load average over the last 15 minutes", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1535723880000, 2.5], - [1535723910000, 2.43], - [1535723940000, 2.35] - ] - }], - "apm_memory": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.beat.memstats.memory_alloc", - "metricAgg": "max", - "label": "Allocated Memory", - "title": "Memory", - "description": "Allocated memory", - "units": "B", - "format": "0,0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1535723880000, 4421336], - [1535723910000, 3888048], - [1535723940000, 3087640] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.beat.memstats.rss", - "metricAgg": "max", - "label": "Process Total", - "title": "Memory", - "description": "Resident set size of memory reserved by the APM service from the OS", - "units": "B", - "format": "0,0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1535723880000, 10260480], - [1535723910000, 11456512], - [1535723940000, 12095488] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.beat.memstats.gc_next", - "metricAgg": "max", - "label": "GC Next", - "title": "Memory", - "description": "Limit of allocated memory at which garbage collection will occur", - "units": "B", - "format": "0,0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [ - [1535723880000, 4996912], - [1535723910000, 4996912], - [1535723940000, 4886176] - ] - }], - "apm_output_events_rate_success": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.total", - "metricAgg": "max", - "label": "Total", - "title": "Output Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 6], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.active", - "metricAgg": "max", - "label": "Active", - "title": "Output Active Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.acked", - "metricAgg": "max", - "label": "Acked", - "title": "Output Acked Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 6], - [1535723940000, 0] - ] - }], - "apm_output_events_rate_failure": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.failed", - "metricAgg": "max", - "label": "Failed", - "title": "Output Failed Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.libbeat.output.events.dropped", - "metricAgg": "max", - "label": "Dropped", - "title": "Output Dropped Events Rate", - "description": "Events processed by the output (including retries)", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }], - "apm_responses_valid": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.count", - "metricAgg": "max", - "label": "Total", - "title": "Response Count Intake API", - "description": "HTTP Requests responded to by server", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 42], - [1535723940000, 42] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.valid.ok", - "metricAgg": "max", - "label": "Ok", - "title": "Ok", - "description": "200 OK response count", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 40], - [1535723940000, 42] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.valid.accepted", - "metricAgg": "max", - "label": "Accepted", - "title": "Accepted", - "description": "HTTP Requests successfully reporting new events", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 2], - [1535723940000, 0] - ] - }], - "apm_responses_errors": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.toolarge", - "metricAgg": "max", - "label": "Too large", - "title": "Response Errors Intake API", - "description": "HTTP Requests rejected due to excessive payload size", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.validate", - "metricAgg": "max", - "label": "Validate", - "title": "Validate", - "description": "HTTP Requests rejected due to payload validation error", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.method", - "metricAgg": "max", - "label": "Method", - "title": "Method", - "description": "HTTP Requests rejected due to incorrect HTTP method", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.unauthorized", - "metricAgg": "max", - "label": "Unauthorized", - "title": "Unauthorized", - "description": "HTTP Requests rejected due to invalid secret token", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.ratelimit", - "metricAgg": "max", - "label": "Rate limit", - "title": "Rate limit", - "description": "HTTP Requests rejected to due excessive rate limit", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.queue", - "metricAgg": "max", - "label": "Queue", - "title": "Queue", - "description": "HTTP Requests rejected to due internal queue filling up", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.decode", - "metricAgg": "max", - "label": "Decode", - "title": "Decode", - "description": "HTTP Requests rejected to due decoding errors - invalid json, incorrect data type for entity", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.forbidden", - "metricAgg": "max", - "label": "Forbidden", - "title": "Forbidden", - "description": "Forbidden HTTP Requests rejected - CORS violation, disabled enpoint", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.concurrency", - "metricAgg": "max", - "label": "Concurrency", - "title": "Concurrency", - "description": "HTTP Requests rejected due to overall concurrency limit breach", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.closed", - "metricAgg": "max", - "label": "Closed", - "title": "Closed", - "description": "HTTP Requests rejected during server shutdown", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.response.errors.internal", - "metricAgg": "max", - "label": "Internal", - "title": "Internal", - "description": "HTTP Requests rejected due to a miscellaneous internal error", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }], - "apm_requests": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.server.request.count", - "metricAgg": "max", - "label": "Requested", - "title": "Request Count Intake API", - "description": "HTTP Requests received by server", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 42], - [1535723940000, 42] - ] - }], - "apm_transformations": [{ - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.processor.transaction.transformations", - "metricAgg": "max", - "label": "Transaction", - "title": "Processed Events", - "description": "Transaction events processed", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 2], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.processor.span.transformations", - "metricAgg": "max", - "label": "Span", - "title": "Transformations", - "description": "Span events processed", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 4], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.processor.error.transformations", - "metricAgg": "max", - "label": "Error", - "title": "Transformations", - "description": "Error events processed", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }, { - "bucket_size": "30 seconds", - "timeRange": { - "min": 1535720389104, - "max": 1535723989104 - }, - "metric": { - "app": "apm", - "field": "beats_stats.metrics.apm-server.processor.metric.transformations", - "metricAgg": "max", - "label": "Metric", - "title": "Transformations", - "description": "Metric events processed", - "units": "/m", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true - }, - "data": [ - [1535723880000, null], - [1535723910000, 0], - [1535723940000, 0] - ] - }] + "apm_cpu": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.beat.cpu.total.value", + "metricAgg": "max", + "label": "Total", + "title": "CPU Utilization", + "description": "Percentage of CPU time spent executing (user+kernel mode) for the APM process", + "units": "%", + "format": "0.[00]", + "hasCalculation": true, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.13333333333333333], + [1535723940000, 0.3] + ] + } + ], + "apm_os_load": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.system.load.1", + "metricAgg": "max", + "label": "1m", + "title": "System Load", + "description": "Load average over the last 1 minute", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1535723880000, 1.37], + [1535723910000, 1.01], + [1535723940000, 0.61] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.system.load.5", + "metricAgg": "max", + "label": "5m", + "title": "System Load", + "description": "Load average over the last 5 minutes", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1535723880000, 1.72], + [1535723910000, 1.6], + [1535723940000, 1.45] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.system.load.15", + "metricAgg": "max", + "label": "15m", + "title": "System Load", + "description": "Load average over the last 15 minutes", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1535723880000, 2.5], + [1535723910000, 2.43], + [1535723940000, 2.35] + ] + } + ], + "apm_memory": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.beat.memstats.memory_alloc", + "metricAgg": "max", + "label": "Allocated Memory", + "title": "Memory", + "description": "Allocated memory", + "units": "B", + "format": "0,0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1535723880000, 4421336], + [1535723910000, 3888048], + [1535723940000, 3087640] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.beat.memstats.rss", + "metricAgg": "max", + "label": "Process Total", + "title": "Memory", + "description": "Resident set size of memory reserved by the APM service from the OS", + "units": "B", + "format": "0,0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1535723880000, 10260480], + [1535723910000, 11456512], + [1535723940000, 12095488] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.beat.memstats.gc_next", + "metricAgg": "max", + "label": "GC Next", + "title": "Memory", + "description": "Limit of allocated memory at which garbage collection will occur", + "units": "B", + "format": "0,0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [1535723880000, 4996912], + [1535723910000, 4996912], + [1535723940000, 4886176] + ] + } + ], + "apm_output_events_rate_success": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.total", + "metricAgg": "max", + "label": "Total", + "title": "Output Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.1], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.active", + "metricAgg": "max", + "label": "Active", + "title": "Output Active Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.acked", + "metricAgg": "max", + "label": "Acked", + "title": "Output Acked Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.1], + [1535723940000, 0] + ] + } + ], + "apm_output_events_rate_failure": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.failed", + "metricAgg": "max", + "label": "Failed", + "title": "Output Failed Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.libbeat.output.events.dropped", + "metricAgg": "max", + "label": "Dropped", + "title": "Output Dropped Events Rate", + "description": "Events processed by the output (including retries)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + } + ], + "apm_responses_valid": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.count", + "metricAgg": "max", + "label": "Total", + "title": "Response Count Intake API", + "description": "HTTP Requests responded to by server", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.7], + [1535723940000, 0.7] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.valid.ok", + "metricAgg": "max", + "label": "Ok", + "title": "Ok", + "description": "200 OK response count", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.6666666666666666], + [1535723940000, 0.7] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.valid.accepted", + "metricAgg": "max", + "label": "Accepted", + "title": "Accepted", + "description": "HTTP Requests successfully reporting new events", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.03333333333333333], + [1535723940000, 0] + ] + } + ], + "apm_responses_errors": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.toolarge", + "metricAgg": "max", + "label": "Too large", + "title": "Response Errors Intake API", + "description": "HTTP Requests rejected due to excessive payload size", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.validate", + "metricAgg": "max", + "label": "Validate", + "title": "Validate", + "description": "HTTP Requests rejected due to payload validation error", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.method", + "metricAgg": "max", + "label": "Method", + "title": "Method", + "description": "HTTP Requests rejected due to incorrect HTTP method", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.unauthorized", + "metricAgg": "max", + "label": "Unauthorized", + "title": "Unauthorized", + "description": "HTTP Requests rejected due to invalid secret token", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.ratelimit", + "metricAgg": "max", + "label": "Rate limit", + "title": "Rate limit", + "description": "HTTP Requests rejected to due excessive rate limit", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.queue", + "metricAgg": "max", + "label": "Queue", + "title": "Queue", + "description": "HTTP Requests rejected to due internal queue filling up", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.decode", + "metricAgg": "max", + "label": "Decode", + "title": "Decode", + "description": "HTTP Requests rejected to due decoding errors - invalid json, incorrect data type for entity", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.forbidden", + "metricAgg": "max", + "label": "Forbidden", + "title": "Forbidden", + "description": "Forbidden HTTP Requests rejected - CORS violation, disabled enpoint", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.concurrency", + "metricAgg": "max", + "label": "Concurrency", + "title": "Concurrency", + "description": "HTTP Requests rejected due to overall concurrency limit breach", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.closed", + "metricAgg": "max", + "label": "Closed", + "title": "Closed", + "description": "HTTP Requests rejected during server shutdown", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.response.errors.internal", + "metricAgg": "max", + "label": "Internal", + "title": "Internal", + "description": "HTTP Requests rejected due to a miscellaneous internal error", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + } + ], + "apm_requests": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.server.request.count", + "metricAgg": "max", + "label": "Requested", + "title": "Request Count Intake API", + "description": "HTTP Requests received by server", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.7], + [1535723940000, 0.7] + ] + } + ], + "apm_transformations": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.processor.transaction.transformations", + "metricAgg": "max", + "label": "Transaction", + "title": "Processed Events", + "description": "Transaction events processed", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.03333333333333333], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.processor.span.transformations", + "metricAgg": "max", + "label": "Span", + "title": "Transformations", + "description": "Span events processed", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0.06666666666666667], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.processor.error.transformations", + "metricAgg": "max", + "label": "Error", + "title": "Transformations", + "description": "Error events processed", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.processor.metric.transformations", + "metricAgg": "max", + "label": "Metric", + "title": "Transformations", + "description": "Metric events processed", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + } + ], + "apm_acm_response": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.count", + "metricAgg": "max", + "label": "Count", + "title": "Response Count Agent Configuration Management", + "description": "HTTP requests responded to by APM Server", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.errors.count", + "metricAgg": "max", + "label": "Error Count", + "title": "Response Error Count Agent Configuration Management", + "description": "HTTP errors count", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.valid.ok", + "metricAgg": "max", + "label": "OK", + "title": "Response OK Count Agent Configuration Management", + "description": "200 OK response count", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.valid.notmodified", + "metricAgg": "max", + "label": "Not Modified", + "title": "Response Not Modified Agent Configuration Management", + "description": "304 Not modified response count", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + } + ], + "apm_acm_response_errors": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.errors.forbidden", + "metricAgg": "max", + "label": "Count", + "title": "Response Errors Agent Configuration Management", + "description": "Forbidden HTTP requests rejected count", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.errors.unauthorized", + "metricAgg": "max", + "label": "Unauthorized", + "title": "Response Unauthorized Errors Agent Configuration Management", + "description": "Unauthorized HTTP requests rejected count", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.errors.unavailable", + "metricAgg": "max", + "label": "Unavailable", + "title": "Response Unavailable Errors Agent Configuration Management", + "description": "Unavailable HTTP response count. Possible misconfiguration or unsupported version of Kibana", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.errors.method", + "metricAgg": "max", + "label": "Method", + "title": "Response Method Errors Agent Configuration Management", + "description": "HTTP requests rejected due to incorrect HTTP method", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + }, + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.response.errors.invalidquery", + "metricAgg": "max", + "label": "Invalid Query", + "title": "Response Invalid Query Errors Agent Configuration Management", + "description": "Invalid HTTP query", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + } + ], + "apm_acm_request_count": [ + { + "bucket_size": "30 seconds", + "timeRange": { "min": 1535720389104, "max": 1535723989104 }, + "metric": { + "app": "apm", + "field": "beats_stats.metrics.apm-server.acm.request.count", + "metricAgg": "max", + "label": "Count", + "title": "Requests Agent Configuration Management", + "description": "HTTP Requests received by agent configuration managemen", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [1535723880000, null], + [1535723910000, 0], + [1535723940000, 0] + ] + } + ] }, "apmSummary": { "uuid": "9b16f434-2092-4983-a401-80a2b61c79d6", From 1158be92643a83cd1ac0905d06a30db3b719d727 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Tue, 23 Jun 2020 15:04:24 -0500 Subject: [PATCH 17/36] [ML] Transform: Table enhancements (#69307) Co-authored-by: Elastic Machine --- x-pack/plugins/ml/public/shared.ts | 3 +- .../transform/common/utils/date_utils.ts | 22 -- .../transform/public/app/common/transform.ts | 2 + .../public/app/common/transform_list.ts | 2 - .../transform/public/app/hooks/use_api.ts | 4 +- .../public/app/hooks/use_pivot_data.ts | 2 +- .../__snapshots__/expanded_row.test.tsx.snap | 291 ------------------ .../transform_list/actions.test.tsx | 1 - .../components/transform_list/actions.tsx | 1 - .../transform_list/columns.test.tsx | 12 +- .../components/transform_list/columns.tsx | 20 +- .../transform_list/expanded_row.test.tsx | 29 +- .../transform_list/expanded_row.tsx | 63 +++- .../transform/public/shared_imports.ts | 1 + .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../apps/transform/creation_index_pattern.ts | 2 - .../apps/transform/creation_saved_search.ts | 2 - .../services/transform/transform_table.ts | 10 - 19 files changed, 92 insertions(+), 381 deletions(-) delete mode 100644 x-pack/plugins/transform/common/utils/date_utils.ts delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row.test.tsx.snap diff --git a/x-pack/plugins/ml/public/shared.ts b/x-pack/plugins/ml/public/shared.ts index ff83d79adff67..4b1d7ee733dcf 100644 --- a/x-pack/plugins/ml/public/shared.ts +++ b/x-pack/plugins/ml/public/shared.ts @@ -14,10 +14,9 @@ export * from '../common/types/audit_message'; export * from '../common/util/anomaly_utils'; export * from '../common/util/errors'; - export * from '../common/util/validators'; export * from './application/formatters/metric_change_description'; - export * from './application/components/data_grid'; export * from './application/data_frame_analytics/common'; +export * from './application/util/date_utils'; diff --git a/x-pack/plugins/transform/common/utils/date_utils.ts b/x-pack/plugins/transform/common/utils/date_utils.ts deleted file mode 100644 index 2acde91413aca..0000000000000 --- a/x-pack/plugins/transform/common/utils/date_utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// utility functions for handling dates - -// @ts-ignore -import { formatDate } from '@elastic/eui/lib/services/format'; - -export function formatHumanReadableDate(ts: number) { - return formatDate(ts, 'MMMM Do YYYY'); -} - -export function formatHumanReadableDateTime(ts: number) { - return formatDate(ts, 'MMMM Do YYYY, HH:mm'); -} - -export function formatHumanReadableDateTimeSeconds(ts: number) { - return formatDate(ts, 'MMMM Do YYYY, HH:mm:ss'); -} diff --git a/x-pack/plugins/transform/public/app/common/transform.ts b/x-pack/plugins/transform/public/app/common/transform.ts index a7c53e76ec5f9..a02bed2fa65e7 100644 --- a/x-pack/plugins/transform/public/app/common/transform.ts +++ b/x-pack/plugins/transform/public/app/common/transform.ts @@ -54,6 +54,8 @@ export interface CreateRequestBody extends PreviewRequestBody { export interface TransformPivotConfig extends CreateRequestBody { id: TransformId; + create_time?: number; + version?: string; } export enum REFRESH_TRANSFORM_LIST_STATE { diff --git a/x-pack/plugins/transform/public/app/common/transform_list.ts b/x-pack/plugins/transform/public/app/common/transform_list.ts index 17d729a453a05..a27fc0b3e0dbb 100644 --- a/x-pack/plugins/transform/public/app/common/transform_list.ts +++ b/x-pack/plugins/transform/public/app/common/transform_list.ts @@ -10,8 +10,6 @@ import { TransformStats } from './transform_stats'; // Used to pass on attribute names to table columns export enum TRANSFORM_LIST_COLUMN { - CONFIG_DEST_INDEX = 'config.dest.index', - CONFIG_SOURCE_INDEX = 'config.source.index', DESCRIPTION = 'config.description', ID = 'id', } diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts index 5d7839cf5fba7..7f6ea817f18d2 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_api.ts @@ -54,7 +54,9 @@ export const useApi = () => { }); }, getTransformsPreview(obj: PreviewRequestBody): Promise { - return http.post(`${API_BASE_PATH}transforms/_preview`, { body: JSON.stringify(obj) }); + return http.post(`${API_BASE_PATH}transforms/_preview`, { + body: JSON.stringify(obj), + }); }, startTransforms( transformsInfo: TransformEndpointRequest[] diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index b073dace30340..a9f34996b9b51 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/common'; import { dictionaryToArray } from '../../../common/types/common'; -import { formatHumanReadableDateTimeSeconds } from '../../../common/utils/date_utils'; +import { formatHumanReadableDateTimeSeconds } from '../../shared_imports'; import { getNestedProperty } from '../../../common/utils/object_utils'; import { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row.test.tsx.snap deleted file mode 100644 index 1f134cd39948b..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row.test.tsx.snap +++ /dev/null @@ -1,291 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Minimal initialization 1`] = ` -, - "data-test-subj": "transformDetailsTab", - "id": "transform-details-tab-fq_date_histogram_1m_1441", - "name": "Transform details", - } - } - onTabClick={[Function]} - size="s" - style={ - Object { - "width": "100%", - } - } - tabs={ - Array [ - Object { - "content": , - "data-test-subj": "transformDetailsTab", - "id": "transform-details-tab-fq_date_histogram_1m_1441", - "name": "Transform details", - }, - Object { - "content": , - "data-test-subj": "transformJsonTab", - "id": "transform-json-tab-fq_date_histogram_1m_1441", - "name": "JSON", - }, - Object { - "content": , - "data-test-subj": "transformMessagesTab", - "id": "transform-messages-tab-fq_date_histogram_1m_1441", - "name": "Messages", - }, - Object { - "content": , - "data-test-subj": "transformPreviewTab", - "id": "transform-preview-tab-fq_date_histogram_1m_1441", - "name": "Preview", - }, - ] - } -/> -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx index aac1d8b5a3f9c..18d324c8767c7 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx @@ -13,7 +13,6 @@ describe('Transform: Transform List Actions', () => { const actions = getActions({ forceDisable: false }); expect(actions).toHaveLength(4); - expect(actions[0].isPrimary).toBeTruthy(); expect(typeof actions[0].render).toBe('function'); expect(typeof actions[1].render).toBe('function'); expect(typeof actions[2].render).toBe('function'); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx index 820b9e0e0d062..343b5e4db67e3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx @@ -19,7 +19,6 @@ import { StopAction } from './action_stop'; export const getActions = ({ forceDisable }: { forceDisable: boolean }) => { return [ { - isPrimary: true, render: (item: TransformListRow) => { if (item.stats.state === TRANSFORM_STATE.STOPPED) { return ; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx index b4198ce3c7244..3c75c33caf840 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx @@ -12,15 +12,13 @@ describe('Transform: Job List Columns', () => { test('getColumns()', () => { const columns = getColumns([], () => {}, []); - expect(columns).toHaveLength(9); + expect(columns).toHaveLength(7); expect(columns[0].isExpander).toBeTruthy(); expect(columns[1].name).toBe('ID'); expect(columns[2].name).toBe('Description'); - expect(columns[3].name).toBe('Source index'); - expect(columns[4].name).toBe('Destination index'); - expect(columns[5].name).toBe('Status'); - expect(columns[6].name).toBe('Mode'); - expect(columns[7].name).toBe('Progress'); - expect(columns[8].name).toBe('Actions'); + expect(columns[3].name).toBe('Status'); + expect(columns[4].name).toBe('Mode'); + expect(columns[5].name).toBe('Progress'); + expect(columns[6].name).toBe('Actions'); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx index 159833354b5ef..5ed2566e8a194 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx @@ -88,8 +88,6 @@ export const getColumns = ( EuiTableComputedColumnType, EuiTableFieldDataColumnType, EuiTableFieldDataColumnType, - EuiTableFieldDataColumnType, - EuiTableFieldDataColumnType, EuiTableComputedColumnType, EuiTableComputedColumnType, EuiTableComputedColumnType, @@ -143,22 +141,6 @@ export const getColumns = ( sortable: true, truncateText: true, }, - { - field: TRANSFORM_LIST_COLUMN.CONFIG_SOURCE_INDEX, - 'data-test-subj': 'transformListColumnSourceIndex', - name: i18n.translate('xpack.transform.sourceIndex', { defaultMessage: 'Source index' }), - sortable: true, - truncateText: true, - }, - { - field: TRANSFORM_LIST_COLUMN.CONFIG_DEST_INDEX, - 'data-test-subj': 'transformListColumnDestinationIndex', - name: i18n.translate('xpack.transform.destinationIndex', { - defaultMessage: 'Destination index', - }), - sortable: true, - truncateText: true, - }, { name: i18n.translate('xpack.transform.status', { defaultMessage: 'Status' }), 'data-test-subj': 'transformListColumnStatus', @@ -242,7 +224,7 @@ export const getColumns = ( { name: i18n.translate('xpack.transform.tableActionLabel', { defaultMessage: 'Actions' }), actions, - width: '200px', + width: '80px', }, ]; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx index 7fcaf5e6048f6..846d8a8ccd200 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx @@ -4,17 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; +import { render, fireEvent } from '@testing-library/react'; import React from 'react'; import moment from 'moment-timezone'; - import { TransformListRow } from '../../../../common'; import { ExpandedRow } from './expanded_row'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; +import { within } from '@testing-library/dom'; -jest.mock('../../../../../shared_imports'); - +jest.mock('../../../../../shared_imports', () => ({ + formatHumanReadableDateTimeSeconds: jest.fn(), +})); describe('Transform: Transform List ', () => { // Set timezone to US/Eastern for consistent test results. beforeEach(() => { @@ -25,11 +26,25 @@ describe('Transform: Transform List ', () => { moment.tz.setDefault('Browser'); }); - test('Minimal initialization', () => { + test('Minimal initialization', async () => { const item: TransformListRow = transformListRow; - const wrapper = shallow(); + const { getByText, getByTestId } = render(); + + expect(getByText('Details')).toBeInTheDocument(); + expect(getByText('Stats')).toBeInTheDocument(); + expect(getByText('JSON')).toBeInTheDocument(); + expect(getByText('Messages')).toBeInTheDocument(); + expect(getByText('Preview')).toBeInTheDocument(); + + const tabContent = getByTestId('transformDetailsTabContent'); + expect(tabContent).toBeInTheDocument(); + + expect(getByTestId('transformDetailsTab')).toHaveAttribute('aria-selected', 'true'); + expect(within(tabContent).getByText('General')).toBeInTheDocument(); - expect(wrapper).toMatchSnapshot(); + fireEvent.click(getByTestId('transformStatsTab')); + expect(getByTestId('transformStatsTab')).toHaveAttribute('aria-selected', 'true'); + expect(within(tabContent).getByText('Stats')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx index 5d58db5b8871e..311aed19e0706 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx @@ -10,8 +10,8 @@ import { EuiTabbedContent } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/utils/date_utils'; - +import moment from 'moment-timezone'; +import { formatHumanReadableDateTimeSeconds } from '../../../../../shared_imports'; import { TransformListRow } from '../../../../common'; import { ExpandedRowDetailsPane, SectionConfig } from './expanded_row_details_pane'; import { ExpandedRowJsonPane } from './expanded_row_json_pane'; @@ -61,6 +61,44 @@ export const ExpandedRow: FC = ({ item }) => { const state: SectionConfig = { title: 'State', items: stateItems, + position: 'right', + }; + + const configItems: Item[] = [ + { + title: 'transform_id', + description: item.id, + }, + { + title: 'transform_version', + description: item.config.version, + }, + { + title: 'description', + description: item.config.description ?? '', + }, + { + title: 'create_time', + description: + formatHumanReadableDateTimeSeconds(moment(item.config.create_time).unix() * 1000) ?? '', + }, + { + title: 'source_index', + description: Array.isArray(item.config.source.index) + ? item.config.source.index[0] + : item.config.source.index, + }, + { + title: 'destination_index', + description: Array.isArray(item.config.dest.index) + ? item.config.dest.index[0] + : item.config.dest.index, + }, + ]; + + const general: SectionConfig = { + title: 'General', + items: configItems, position: 'left', }; @@ -108,7 +146,7 @@ export const ExpandedRow: FC = ({ item }) => { const checkpointing: SectionConfig = { title: 'Checkpointing', items: checkpointingItems, - position: 'left', + position: 'right', }; const stats: SectionConfig = { @@ -116,7 +154,7 @@ export const ExpandedRow: FC = ({ item }) => { items: Object.entries(item.stats.stats).map((s) => { return { title: s[0].toString(), description: getItemDescription(s[1]) }; }), - position: 'right', + position: 'left', }; const tabs = [ @@ -124,12 +162,23 @@ export const ExpandedRow: FC = ({ item }) => { id: `transform-details-tab-${item.id}`, 'data-test-subj': 'transformDetailsTab', name: i18n.translate( - 'xpack.transform.transformList.transformDetails.tabs.transformSettingsLabel', + 'xpack.transform.transformList.transformDetails.tabs.transformDetailsLabel', + { + defaultMessage: 'Details', + } + ), + content: , + }, + { + id: `transform-stats-tab-${item.id}`, + 'data-test-subj': 'transformStatsTab', + name: i18n.translate( + 'xpack.transform.transformList.transformDetails.tabs.transformStatsLabel', { - defaultMessage: 'Transform details', + defaultMessage: 'Stats', } ), - content: , + content: , }, { id: `transform-json-tab-${item.id}`, diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index f50ae9274fa4a..e0bbcd0b5d9db 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -17,6 +17,7 @@ export { fetchChartsData, getErrorMessage, extractErrorMessage, + formatHumanReadableDateTimeSeconds, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, multiColumnSortFactory, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d6999c3f12cfa..76650bf421f22 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15793,7 +15793,6 @@ "xpack.transform.cloneTransform.breadcrumbTitle": "クローン変換", "xpack.transform.createTransform.breadcrumbTitle": "変換の作成", "xpack.transform.description": "説明", - "xpack.transform.destinationIndex": "デスティネーションインデックス", "xpack.transform.groupby.popoverForm.aggLabel": "集約", "xpack.transform.groupBy.popoverForm.aggNameAlreadyUsedError": "別のグループ分けの構成が既にこの名前を使用しています。", "xpack.transform.groupBy.popoverForm.aggNameInvalidCharError": "無効な名前です。「[」、「]」「>」は使用できず、名前の始めと終わりにはスペースを使用できません。", @@ -15828,7 +15827,6 @@ "xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody": "プレビューリクエストはデータを返しませんでした。オプションのクエリがデータを返し、グループ分け基準により使用されるフィールドと集約フィールドに値が存在することを確認してください。", "xpack.transform.pivotPreview.PivotPreviewTitle": "ピボットプレビューを変換", "xpack.transform.progress": "進捗", - "xpack.transform.sourceIndex": "ソースインデックス", "xpack.transform.statsBar.batchTransformsLabel": "一斉", "xpack.transform.statsBar.continuousTransformsLabel": "連続", "xpack.transform.statsBar.failedTransformsLabel": "失敗", @@ -15989,7 +15987,6 @@ "xpack.transform.transformList.transformDetails.messagesPane.timeLabel": "時間", "xpack.transform.transformList.transformDetails.tabs.transformMessagesLabel": "メッセージ", "xpack.transform.transformList.transformDetails.tabs.transformPreviewLabel": "プレビュー", - "xpack.transform.transformList.transformDetails.tabs.transformSettingsLabel": "ジョブの詳細", "xpack.transform.transformList.transformDocsLinkText": "変換ドキュメント", "xpack.transform.transformList.transformTitle": "データフレームジョブ", "xpack.transform.transformsDescription": "変換を使用して、集約されたインデックスまたはエンティティ中心のインデックスに、既存のElasticsearchインデックスをインデックスします。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 985d85338d0a1..dc20275561cb0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15798,7 +15798,6 @@ "xpack.transform.cloneTransform.breadcrumbTitle": "克隆转换", "xpack.transform.createTransform.breadcrumbTitle": "创建转换", "xpack.transform.description": "描述", - "xpack.transform.destinationIndex": "目标 IP", "xpack.transform.groupby.popoverForm.aggLabel": "聚合", "xpack.transform.groupBy.popoverForm.aggNameAlreadyUsedError": "其他分组依据配置已使用该名称。", "xpack.transform.groupBy.popoverForm.aggNameInvalidCharError": "名称无效。不允许使用字符“[”、“]”和“>”,且名称不得以空格字符开头或结束。", @@ -15833,7 +15832,6 @@ "xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody": "预览请求未返回任何数据。请确保可选查询返回数据且存在分组依据和聚合字段使用的字段的值。", "xpack.transform.pivotPreview.PivotPreviewTitle": "转换数据透视表预览", "xpack.transform.progress": "进度", - "xpack.transform.sourceIndex": "源索引", "xpack.transform.statsBar.batchTransformsLabel": "批量", "xpack.transform.statsBar.continuousTransformsLabel": "连续", "xpack.transform.statsBar.failedTransformsLabel": "失败", @@ -15994,7 +15992,6 @@ "xpack.transform.transformList.transformDetails.messagesPane.timeLabel": "时间", "xpack.transform.transformList.transformDetails.tabs.transformMessagesLabel": "消息", "xpack.transform.transformList.transformDetails.tabs.transformPreviewLabel": "预览", - "xpack.transform.transformList.transformDetails.tabs.transformSettingsLabel": "作业详情", "xpack.transform.transformList.transformDocsLinkText": "转换文档", "xpack.transform.transformList.transformTitle": "数据帧作业", "xpack.transform.transformsDescription": "使用转换将现有 Elasticsearch 索引透视成摘要式或以实体为中心的索引。", diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index 55b2f4a0220ad..bf267c80cdcce 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -436,8 +436,6 @@ export default function ({ getService }: FtrProviderContext) { await transform.table.assertTransformRowFields(testData.transformId, { id: testData.transformId, description: testData.transformDescription, - sourceIndex: testData.source, - destinationIndex: testData.destinationIndex, status: testData.expected.row.status, mode: testData.expected.row.mode, progress: testData.expected.row.progress, diff --git a/x-pack/test/functional/apps/transform/creation_saved_search.ts b/x-pack/test/functional/apps/transform/creation_saved_search.ts index ad62f06d1f3cd..bc4ded49660f4 100644 --- a/x-pack/test/functional/apps/transform/creation_saved_search.ts +++ b/x-pack/test/functional/apps/transform/creation_saved_search.ts @@ -239,8 +239,6 @@ export default function ({ getService }: FtrProviderContext) { await transform.table.assertTransformRowFields(testData.transformId, { id: testData.transformId, description: testData.transformDescription, - sourceIndex: testData.expected.sourceIndex, - destinationIndex: testData.destinationIndex, status: testData.expected.row.status, mode: testData.expected.row.mode, progress: testData.expected.row.progress, diff --git a/x-pack/test/functional/services/transform/transform_table.ts b/x-pack/test/functional/services/transform/transform_table.ts index 3155ef0b26050..0c9a5414bdd2b 100644 --- a/x-pack/test/functional/services/transform/transform_table.ts +++ b/x-pack/test/functional/services/transform/transform_table.ts @@ -31,16 +31,6 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { .find('.euiTableCellContent') .text() .trim(), - sourceIndex: $tr - .findTestSubject('transformListColumnSourceIndex') - .find('.euiTableCellContent') - .text() - .trim(), - destinationIndex: $tr - .findTestSubject('transformListColumnDestinationIndex') - .find('.euiTableCellContent') - .text() - .trim(), status: $tr .findTestSubject('transformListColumnStatus') .find('.euiTableCellContent') From 22d09a3bbd02851bcfe2e7a4e32d752f399ed400 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Tue, 23 Jun 2020 15:05:44 -0500 Subject: [PATCH 18/36] [ML] Transform: Enable force delete if one of the transforms failed (#69472) Co-authored-by: Elastic Machine --- x-pack/plugins/transform/common/index.ts | 1 + .../transform/public/app/hooks/use_api.ts | 10 ++- .../public/app/hooks/use_delete_transform.tsx | 15 +++-- .../transform_list/action_delete.tsx | 64 +++++++++++++------ .../server/client/elasticsearch_transform.ts | 5 +- .../transform/server/routes/api/schema.ts | 1 + .../transform/server/routes/api/transforms.ts | 26 +++----- 7 files changed, 77 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/transform/common/index.ts b/x-pack/plugins/transform/common/index.ts index 79ff6298a2ca2..08bb4022c7016 100644 --- a/x-pack/plugins/transform/common/index.ts +++ b/x-pack/plugins/transform/common/index.ts @@ -43,6 +43,7 @@ export interface DeleteTransformEndpointRequest { transformsInfo: TransformEndpointRequest[]; deleteDestIndex?: boolean; deleteDestIndexPattern?: boolean; + forceDelete?: boolean; } export interface DeleteTransformStatus { diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts index 7f6ea817f18d2..56528370a3ab9 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_api.ts @@ -47,10 +47,16 @@ export const useApi = () => { deleteTransforms( transformsInfo: TransformEndpointRequest[], deleteDestIndex: boolean | undefined, - deleteDestIndexPattern: boolean | undefined + deleteDestIndexPattern: boolean | undefined, + forceDelete: boolean ): Promise { return http.post(`${API_BASE_PATH}delete_transforms`, { - body: JSON.stringify({ transformsInfo, deleteDestIndex, deleteDestIndexPattern }), + body: JSON.stringify({ + transformsInfo, + deleteDestIndex, + deleteDestIndexPattern, + forceDelete, + }), }); }, getTransformsPreview(obj: PreviewRequestBody): Promise { diff --git a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx index 1f395e67b7d31..43c5ae6fad1b1 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx @@ -8,13 +8,13 @@ import React, { useCallback, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { - TransformEndpointRequest, DeleteTransformEndpointResult, DeleteTransformStatus, + TransformEndpointRequest, } from '../../../common'; -import { getErrorMessage, extractErrorMessage } from '../../shared_imports'; +import { extractErrorMessage, getErrorMessage } from '../../shared_imports'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; -import { TransformListRow, refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; +import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$, TransformListRow } from '../common'; import { ToastNotificationText } from '../components'; import { useApi } from './use_api'; import { indexService } from '../services/es_index_service'; @@ -27,13 +27,13 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { const [deleteIndexPattern, setDeleteIndexPattern] = useState(true); const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false); const [indexPatternExists, setIndexPatternExists] = useState(false); + const toggleDeleteIndex = useCallback(() => setDeleteDestIndex(!deleteDestIndex), [ deleteDestIndex, ]); const toggleDeleteIndexPattern = useCallback(() => setDeleteIndexPattern(!deleteIndexPattern), [ deleteIndexPattern, ]); - const checkIndexPatternExists = useCallback( async (indexName: string) => { try { @@ -79,6 +79,7 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { useEffect(() => { checkUserIndexPermission(); + // if user only deleting one transform if (items.length === 1) { const config = items[0].config; const destinationIndex = Array.isArray(config.dest.index) @@ -110,7 +111,8 @@ export const useDeleteTransforms = () => { return async ( transforms: TransformListRow[], shouldDeleteDestIndex: boolean, - shouldDeleteDestIndexPattern: boolean + shouldDeleteDestIndexPattern: boolean, + shouldForceDelete = false ) => { const transformsInfo: TransformEndpointRequest[] = transforms.map((tf) => ({ id: tf.config.id, @@ -121,7 +123,8 @@ export const useDeleteTransforms = () => { const results: DeleteTransformEndpointResult = await api.deleteTransforms( transformsInfo, shouldDeleteDestIndex, - shouldDeleteDestIndexPattern + shouldDeleteDestIndexPattern, + shouldForceDelete ); const isBulk = Object.keys(results).length > 1; const successCount: Record = { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.tsx index d7db55990d333..19297eb25d0bd 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.tsx @@ -4,25 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useContext, useState } from 'react'; +import React, { FC, Fragment, useContext, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { + EUI_MODAL_CONFIRM_BUTTON, EuiButtonEmpty, EuiConfirmModal, - EuiOverlayMask, - EuiToolTip, - EUI_MODAL_CONFIRM_BUTTON, EuiFlexGroup, EuiFlexItem, - EuiSwitch, + EuiOverlayMask, EuiSpacer, + EuiSwitch, + EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { TRANSFORM_STATE } from '../../../../../../common'; -import { useDeleteTransforms, useDeleteIndexAndTargetIndex } from '../../../../hooks'; +import { useDeleteIndexAndTargetIndex, useDeleteTransforms } from '../../../../hooks'; import { - createCapabilityFailureMessage, AuthorizationContext, + createCapabilityFailureMessage, } from '../../../../lib/authorization'; import { TransformListRow } from '../../../../common'; @@ -31,11 +31,17 @@ interface DeleteActionProps { forceDisable?: boolean; } +const transformCanNotBeDeleted = (i: TransformListRow) => + ![TRANSFORM_STATE.STOPPED, TRANSFORM_STATE.FAILED].includes(i.stats.state); + export const DeleteAction: FC = ({ items, forceDisable }) => { const isBulkAction = items.length > 1; - const disabled = items.some((i: TransformListRow) => i.stats.state !== TRANSFORM_STATE.STOPPED); - + const disabled = items.some(transformCanNotBeDeleted); + const shouldForceDelete = useMemo( + () => items.some((i: TransformListRow) => i.stats.state === TRANSFORM_STATE.FAILED), + [items] + ); const { canDeleteTransform } = useContext(AuthorizationContext).capabilities; const deleteTransforms = useDeleteTransforms(); const { @@ -56,7 +62,12 @@ export const DeleteAction: FC = ({ items, forceDisable }) => const shouldDeleteDestIndex = userCanDeleteIndex && deleteDestIndex; const shouldDeleteDestIndexPattern = userCanDeleteIndex && indexPatternExists && deleteIndexPattern; - deleteTransforms(items, shouldDeleteDestIndex, shouldDeleteDestIndexPattern); + // if we are deleting multiple transforms, then force delete all if at least one item has failed + // else, force delete only when the item user picks has failed + const forceDelete = isBulkAction + ? shouldForceDelete + : items[0] && items[0] && items[0].stats.state === TRANSFORM_STATE.FAILED; + deleteTransforms(items, shouldDeleteDestIndex, shouldDeleteDestIndexPattern, forceDelete); }; const openModal = () => setModalVisible(true); @@ -89,11 +100,19 @@ export const DeleteAction: FC = ({ items, forceDisable }) => const bulkDeleteModalContent = ( <>

- + {shouldForceDelete ? ( + + ) : ( + + )}

@@ -134,10 +153,17 @@ export const DeleteAction: FC = ({ items, forceDisable }) => const deleteModalContent = ( <>

- + {items[0] && items[0] && items[0].stats.state === TRANSFORM_STATE.FAILED ? ( + + ) : ( + + )}

diff --git a/x-pack/plugins/transform/server/client/elasticsearch_transform.ts b/x-pack/plugins/transform/server/client/elasticsearch_transform.ts index 91c00f5eb5df2..a17eb1416408a 100644 --- a/x-pack/plugins/transform/server/client/elasticsearch_transform.ts +++ b/x-pack/plugins/transform/server/client/elasticsearch_transform.ts @@ -83,11 +83,14 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) transform.deleteTransform = ca({ urls: [ { - fmt: '/_transform/<%=transformId%>', + fmt: '/_transform/<%=transformId%>?&force=<%=force%>', req: { transformId: { type: 'string', }, + force: { + type: 'boolean', + }, }, }, ], diff --git a/x-pack/plugins/transform/server/routes/api/schema.ts b/x-pack/plugins/transform/server/routes/api/schema.ts index cf39f2e3829ea..7da3f1ccfe55e 100644 --- a/x-pack/plugins/transform/server/routes/api/schema.ts +++ b/x-pack/plugins/transform/server/routes/api/schema.ts @@ -27,4 +27,5 @@ export const deleteTransformSchema = schema.object({ ), deleteDestIndex: schema.maybe(schema.boolean()), deleteDestIndexPattern: schema.maybe(schema.boolean()), + forceDelete: schema.maybe(schema.boolean()), }); diff --git a/x-pack/plugins/transform/server/routes/api/transforms.ts b/x-pack/plugins/transform/server/routes/api/transforms.ts index 93fda56d319ad..efbe813db5e67 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms.ts @@ -190,6 +190,7 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { transformsInfo, deleteDestIndex, deleteDestIndexPattern, + forceDelete, } = req.body as DeleteTransformEndpointRequest; try { @@ -197,6 +198,7 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { transformsInfo, deleteDestIndex, deleteDestIndexPattern, + forceDelete, ctx, license, res @@ -295,39 +297,28 @@ async function deleteTransforms( transformsInfo: TransformEndpointRequest[], deleteDestIndex: boolean | undefined, deleteDestIndexPattern: boolean | undefined, + shouldForceDelete: boolean = false, ctx: RequestHandlerContext, license: RouteDependencies['license'], response: KibanaResponseFactory ) { - const tempResults: TransformEndpointResult = {}; const results: Record = {}; for (const transformInfo of transformsInfo) { let destinationIndex: string | undefined; + const transformDeleted: ResultData = { success: false }; const destIndexDeleted: ResultData = { success: false }; const destIndexPatternDeleted: ResultData = { success: false, }; const transformId = transformInfo.id; + // force delete only if the transform has failed + let needToForceDelete = false; + try { if (transformInfo.state === TRANSFORM_STATE.FAILED) { - try { - await ctx.transform!.dataClient.callAsCurrentUser('transform.stopTransform', { - transformId, - force: true, - waitForCompletion: true, - } as StopOptions); - } catch (e) { - if (isRequestTimeout(e)) { - return fillResultsWithTimeouts({ - results: tempResults, - id: transformId, - items: transformsInfo, - action: TRANSFORM_ACTIONS.DELETE, - }); - } - } + needToForceDelete = true; } // Grab destination index info to delete try { @@ -383,6 +374,7 @@ async function deleteTransforms( try { await ctx.transform!.dataClient.callAsCurrentUser('transform.deleteTransform', { transformId, + force: shouldForceDelete && needToForceDelete, }); transformDeleted.success = true; } catch (deleteTransformJobError) { From e87a4b2a31c2d7c5397e25d0d720e4607df71408 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 23 Jun 2020 16:19:07 -0400 Subject: [PATCH 19/36] fix link to analytics results from management (#69550) --- .../analytics_list/action_clone.tsx | 2 +- .../components/analytics_list/actions.tsx | 198 +++++++++++------- .../components/analytics_list/columns.tsx | 5 +- .../components/analytics_list/common.ts | 2 +- .../jobs_list_page/jobs_list_page.tsx | 88 ++++---- .../application/management/jobs_list/index.ts | 3 +- .../components/analytics_panel/table.tsx | 4 +- 7 files changed, 169 insertions(+), 133 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx index df7dce7217fd4..f184c7c5d874e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx @@ -344,7 +344,7 @@ export function getCloneAction(createAnalyticsForm: CreateAnalyticsFormProps) { } interface CloneActionProps { - item: DeepReadonly; + item: DataFrameAnalyticsListRow; createAnalyticsForm: CreateAnalyticsFormProps; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index ff0658e8daccd..b47b23f668530 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; -import { DeepReadonly } from '../../../../../../../common/types/common'; import { checkPermission, @@ -21,6 +20,7 @@ import { isClassificationAnalysis, } from '../../../../common/analytics'; import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; +import { useMlKibana } from '../../../../../contexts/kibana'; import { CloneAction } from './action_clone'; import { getResultsUrl, isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; @@ -29,87 +29,123 @@ import { stopAnalytics } from '../../services/analytics_service'; import { StartAction } from './action_start'; import { DeleteAction } from './action_delete'; -export const AnalyticsViewAction = { - isPrimary: true, - render: (item: DataFrameAnalyticsListRow) => { - const analysisType = getAnalysisType(item.config.analysis); - const isDisabled = - !isRegressionAnalysis(item.config.analysis) && - !isOutlierAnalysis(item.config.analysis) && - !isClassificationAnalysis(item.config.analysis); - - const url = getResultsUrl(item.id, analysisType); - return ( - (window.location.href = url)} - size="xs" - color="text" - iconType="visTable" - aria-label={i18n.translate('xpack.ml.dataframe.analyticsList.viewAriaLabel', { - defaultMessage: 'View', - })} - data-test-subj="mlAnalyticsJobViewButton" - > - {i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', { - defaultMessage: 'View', - })} - - ); - }, +interface Props { + item: DataFrameAnalyticsListRow; + isManagementTable: boolean; +} + +const AnalyticsViewButton: FC = ({ item, isManagementTable }) => { + const { + services: { + application: { navigateToUrl, navigateToApp }, + }, + } = useMlKibana(); + + const analysisType = getAnalysisType(item.config.analysis); + const isDisabled = + !isRegressionAnalysis(item.config.analysis) && + !isOutlierAnalysis(item.config.analysis) && + !isClassificationAnalysis(item.config.analysis); + + const url = getResultsUrl(item.id, analysisType); + const navigator = isManagementTable + ? () => navigateToApp('ml', { path: url }) + : () => navigateToUrl(url); + + return ( + + {i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', { + defaultMessage: 'View', + })} + + ); }; -export const getActions = (createAnalyticsForm: CreateAnalyticsFormProps) => { +interface Action { + isPrimary?: boolean; + render: (item: DataFrameAnalyticsListRow) => any; +} + +export const getAnalyticsViewAction = (isManagementTable: boolean = false): Action => ({ + isPrimary: true, + render: (item: DataFrameAnalyticsListRow) => ( + + ), +}); + +export const getActions = ( + createAnalyticsForm: CreateAnalyticsFormProps, + isManagementTable: boolean +) => { const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics'); + const actions: Action[] = [getAnalyticsViewAction(isManagementTable)]; - return [ - AnalyticsViewAction, - { - render: (item: DataFrameAnalyticsListRow) => { - if (!isDataFrameAnalyticsRunning(item.stats.state)) { - return ; - } - - const buttonStopText = i18n.translate('xpack.ml.dataframe.analyticsList.stopActionName', { - defaultMessage: 'Stop', - }); - - const stopButton = ( - stopAnalytics(item)} - aria-label={buttonStopText} - data-test-subj="mlAnalyticsJobStopButton" - > - {buttonStopText} - - ); - if (!canStartStopDataFrameAnalytics) { - return ( - - {stopButton} - - ); - } - - return stopButton; - }, - }, - { - render: (item: DataFrameAnalyticsListRow) => { - return ; - }, - }, - { - render: (item: DeepReadonly) => { - return ; - }, - }, - ]; + if (isManagementTable === false) { + actions.push( + ...[ + { + render: (item: DataFrameAnalyticsListRow) => { + if (!isDataFrameAnalyticsRunning(item.stats.state)) { + return ; + } + + const buttonStopText = i18n.translate( + 'xpack.ml.dataframe.analyticsList.stopActionName', + { + defaultMessage: 'Stop', + } + ); + + const stopButton = ( + stopAnalytics(item)} + aria-label={buttonStopText} + data-test-subj="mlAnalyticsJobStopButton" + > + {buttonStopText} + + ); + if (!canStartStopDataFrameAnalytics) { + return ( + + {stopButton} + + ); + } + + return stopButton; + }, + }, + { + render: (item: DataFrameAnalyticsListRow) => { + return ; + }, + }, + { + render: (item: DataFrameAnalyticsListRow) => { + return ; + }, + }, + ] + ); + } + + return actions; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 236a8083a95e6..a3d2e65386c19 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -33,7 +33,7 @@ import { DataFrameAnalyticsListRow, DataFrameAnalyticsStats, } from './common'; -import { getActions, AnalyticsViewAction } from './actions'; +import { getActions } from './actions'; enum TASK_STATE_COLOR { analyzing = 'primary', @@ -148,8 +148,7 @@ export const getColumns = ( isMlEnabledInSpace: boolean = true, createAnalyticsForm?: CreateAnalyticsFormProps ) => { - const actions = - isManagementTable === true ? [AnalyticsViewAction] : getActions(createAnalyticsForm!); + const actions = getActions(createAnalyticsForm!, isManagementTable); function toggleDetails(item: DataFrameAnalyticsListRow) { const index = expandedRowItemIds.indexOf(item.config.id); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts index e0622efe35ab6..5998c62eeacea 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts @@ -122,5 +122,5 @@ export function isCompletedAnalyticsJob(stats: DataFrameAnalyticsStats) { } export function getResultsUrl(jobId: string, analysisType: string) { - return `ml#/data_frame_analytics/exploration?_g=(ml:(jobId:${jobId},analysisType:${analysisType}))`; + return `#/data_frame_analytics/exploration?_g=(ml:(jobId:${jobId},analysisType:${analysisType}))`; } diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index 4a41f3e45001d..e3c45c6cd0b04 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -21,6 +21,7 @@ import { } from '@elastic/eui'; import { checkGetManagementMlJobsResolver } from '../../../../capabilities/check_capabilities'; +import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public'; import { getDocLinks } from '../../../../util/dependency_cache'; // @ts-ignore undeclared module @@ -65,13 +66,12 @@ function getTabs(isMlEnabledInSpace: boolean): Tab[] { ]; } -export const JobsListPage: FC<{ I18nContext: CoreStart['i18n']['Context'] }> = ({ - I18nContext, -}) => { +export const JobsListPage: FC<{ coreStart: CoreStart }> = ({ coreStart }) => { const [initialized, setInitialized] = useState(false); const [isMlEnabledInSpace, setIsMlEnabledInSpace] = useState(false); const tabs = getTabs(isMlEnabledInSpace); const [currentTabId, setCurrentTabId] = useState(tabs[0].id); + const I18nContext = coreStart.i18n.Context; const check = async () => { try { @@ -122,46 +122,48 @@ export const JobsListPage: FC<{ I18nContext: CoreStart['i18n']['Context'] }> = ( return ( - - - - -

- {i18n.translate('xpack.ml.management.jobsList.jobsListTitle', { - defaultMessage: 'Machine Learning Jobs', - })} -

-
- - - {currentTabId === 'anomaly_detection_jobs' - ? anomalyDetectionDocsLabel - : analyticsDocsLabel} - - -
-
- - - - {i18n.translate('xpack.ml.management.jobsList.jobsListTagline', { - defaultMessage: 'View machine learning analytics and anomaly detection jobs.', - })} - - - - {renderTabs()} -
+ + + + + +

+ {i18n.translate('xpack.ml.management.jobsList.jobsListTitle', { + defaultMessage: 'Machine Learning Jobs', + })} +

+
+ + + {currentTabId === 'anomaly_detection_jobs' + ? anomalyDetectionDocsLabel + : analyticsDocsLabel} + + +
+
+ + + + {i18n.translate('xpack.ml.management.jobsList.jobsListTagline', { + defaultMessage: 'View machine learning analytics and anomaly detection jobs.', + })} + + + + {renderTabs()} +
+
); }; diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts index 5d1fc6f0a3c92..b16f680a2a362 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts +++ b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts @@ -14,8 +14,7 @@ import { getJobsListBreadcrumbs } from '../breadcrumbs'; import { setDependencyCache, clearCache } from '../../util/dependency_cache'; const renderApp = (element: HTMLElement, coreStart: CoreStart) => { - const I18nContext = coreStart.i18n.Context; - ReactDOM.render(React.createElement(JobsListPage, { I18nContext }), element); + ReactDOM.render(React.createElement(JobsListPage, { coreStart }), element); return () => { unmountComponentAtNode(element); clearCache(); diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx index c3b8e8dd4e27f..f2e6ff7885b16 100644 --- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx @@ -23,7 +23,7 @@ import { getTaskStateBadge, progressColumn, } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/columns'; -import { AnalyticsViewAction } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; +import { getAnalyticsViewAction } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; const MlInMemoryTable = mlInMemoryTableFactory(); @@ -82,7 +82,7 @@ export const AnalyticsTable: FC = ({ items }) => { name: i18n.translate('xpack.ml.overview.analyticsList.tableActionLabel', { defaultMessage: 'Actions', }), - actions: [AnalyticsViewAction], + actions: [getAnalyticsViewAction()], width: '100px', }, ]; From 71d54c8caed0dda26d80e8e1f1c38f212e212dc8 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 23 Jun 2020 22:30:02 +0200 Subject: [PATCH 20/36] adds kibana navigation tests (#69733) --- .../cypress/integration/cases.spec.ts | 4 +- .../integration/cases_connectors.spec.ts | 4 +- .../cypress/integration/events_viewer.spec.ts | 12 +-- .../integration/fields_browser.spec.ts | 6 +- .../cypress/integration/inspect.spec.ts | 8 +- .../cypress/integration/navigation.spec.ts | 80 ++++++++++++++++--- .../cypress/integration/overview.spec.ts | 4 +- .../cypress/integration/search_bar.spec.ts | 4 +- .../timeline_data_providers.spec.ts | 4 +- .../timeline_flyout_button.spec.ts | 4 +- .../timeline_local_storage.spec.ts | 4 +- .../timeline_search_or_filter.spec.ts | 4 +- .../timeline_toggle_column.spec.ts | 4 +- .../cypress/integration/url_state.spec.ts | 4 +- .../cypress/screens/kibana_navigation.ts | 22 +++++ .../cypress/tasks/kibana_navigation.ts | 15 ++++ .../cypress/urls/navigation.ts | 12 +-- 17 files changed, 148 insertions(+), 47 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts create mode 100644 x-pack/plugins/security_solution/cypress/tasks/kibana_navigation.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts index bb2dffe8ddd7d..efd9ece8aec56 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts @@ -44,7 +44,7 @@ import { backToCases, createNewCase } from '../tasks/create_new_case'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; -import { CASES } from '../urls/navigation'; +import { CASES_URL } from '../urls/navigation'; describe('Cases', () => { before(() => { @@ -56,7 +56,7 @@ describe('Cases', () => { }); it('Creates a new case with timeline and opens the timeline', () => { - loginAndWaitForPageWithoutDateRange(CASES); + loginAndWaitForPageWithoutDateRange(CASES_URL); goToCreateNewCase(); createNewCase(case1); backToCases(); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts index 266d183ea1b85..ed885ad653e5d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts @@ -15,7 +15,7 @@ import { } from '../tasks/configure_cases'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { CASES } from '../urls/navigation'; +import { CASES_URL } from '../urls/navigation'; describe('Cases connectors', () => { before(() => { @@ -25,7 +25,7 @@ describe('Cases connectors', () => { }); it('Configures a new connector', () => { - loginAndWaitForPageWithoutDateRange(CASES); + loginAndWaitForPageWithoutDateRange(CASES_URL); goToEditExternalConnection(); openAddNewConnectorOption(); addServiceNowConnector(serviceNowConnector); diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts index b2d35f3f0c336..cd4573817cc27 100644 --- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts @@ -33,7 +33,7 @@ import { } from '../tasks/hosts/events'; import { clearSearchBar, kqlSearch } from '../tasks/security_header'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; import { resetFields } from '../tasks/timeline'; const defaultHeadersInDefaultEcsCategory = [ @@ -49,7 +49,7 @@ const defaultHeadersInDefaultEcsCategory = [ describe('Events Viewer', () => { context('Fields rendering', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -75,7 +75,7 @@ describe('Events Viewer', () => { context('Events viewer query modal', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -93,7 +93,7 @@ describe('Events Viewer', () => { context('Events viewer fields behaviour', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -124,7 +124,7 @@ describe('Events Viewer', () => { context('Events behaviour', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); }); @@ -155,7 +155,7 @@ describe('Events Viewer', () => { context.skip('Events columns', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts index b0dbf94c0efb9..6438a738580b7 100644 --- a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts @@ -31,7 +31,7 @@ import { loginAndWaitForPage } from '../tasks/login'; import { openTimeline } from '../tasks/security_main'; import { openTimelineFieldsBrowser, populateTimeline } from '../tasks/timeline'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; const defaultHeaders = [ { id: '@timestamp' }, @@ -47,7 +47,7 @@ const defaultHeaders = [ describe('Fields Browser', () => { context('Fields Browser rendering', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openTimeline(); populateTimeline(); openTimelineFieldsBrowser(); @@ -110,7 +110,7 @@ describe('Fields Browser', () => { context('Editing the timeline', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openTimeline(); populateTimeline(); openTimelineFieldsBrowser(); diff --git a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts index 0529c797ee07a..53ddff501db82 100644 --- a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts @@ -19,12 +19,12 @@ import { openTimelineSettings, } from '../tasks/timeline'; -import { HOSTS_PAGE, NETWORK_PAGE } from '../urls/navigation'; +import { HOSTS_URL, NETWORK_URL } from '../urls/navigation'; describe('Inspect', () => { context('Hosts stats and tables', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); }); afterEach(() => { closesModal(); @@ -40,7 +40,7 @@ describe('Inspect', () => { context('Network stats and tables', () => { before(() => { - loginAndWaitForPage(NETWORK_PAGE); + loginAndWaitForPage(NETWORK_URL); }); afterEach(() => { closesModal(); @@ -57,7 +57,7 @@ describe('Inspect', () => { context('Timeline', () => { it('inspects the timeline', () => { const hostExistsQuery = 'host.name: *'; - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openTimeline(); executeTimelineKQL(hostExistsQuery); openTimelineSettings(); diff --git a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts index 28ae42f8c0974..ea3a78c77152a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts @@ -16,45 +16,107 @@ import { import { loginAndWaitForPage } from '../tasks/login'; import { navigateFromHeaderTo } from '../tasks/security_header'; -import { TIMELINES_PAGE } from '../urls/navigation'; +import { + ALERTS_URL, + CASES_URL, + HOSTS_URL, + KIBANA_HOME, + MANAGEMENT_URL, + NETWORK_URL, + OVERVIEW_URL, + TIMELINES_URL, +} from '../urls/navigation'; +import { openKibanaNavigation, navigateFromKibanaCollapsibleTo } from '../tasks/kibana_navigation'; +import { + ALERTS_PAGE, + CASES_PAGE, + HOSTS_PAGE, + MANAGEMENT_PAGE, + NETWORK_PAGE, + OVERVIEW_PAGE, + TIMELINES_PAGE, +} from '../screens/kibana_navigation'; describe('top-level navigation common to all pages in the Security app', () => { before(() => { - loginAndWaitForPage(TIMELINES_PAGE); + loginAndWaitForPage(TIMELINES_URL); }); it('navigates to the Overview page', () => { navigateFromHeaderTo(OVERVIEW); - cy.url().should('include', '/security/overview'); + cy.url().should('include', OVERVIEW_URL); }); it('navigates to the Alerts page', () => { navigateFromHeaderTo(ALERTS); - cy.url().should('include', '/security/alerts'); + cy.url().should('include', ALERTS_URL); }); it('navigates to the Hosts page', () => { navigateFromHeaderTo(HOSTS); - cy.url().should('include', '/security/hosts'); + cy.url().should('include', HOSTS_URL); }); it('navigates to the Network page', () => { navigateFromHeaderTo(NETWORK); - cy.url().should('include', '/security/network'); + cy.url().should('include', NETWORK_URL); }); it('navigates to the Timelines page', () => { navigateFromHeaderTo(TIMELINES); - cy.url().should('include', '/security/timelines'); + cy.url().should('include', TIMELINES_URL); }); it('navigates to the Cases page', () => { navigateFromHeaderTo(CASES); - cy.url().should('include', '/security/cases'); + cy.url().should('include', CASES_URL); }); it('navigates to the Management page', () => { navigateFromHeaderTo(MANAGEMENT); - cy.url().should('include', '/security/management'); + cy.url().should('include', MANAGEMENT_URL); + }); +}); + +describe('Kibana navigation to all pages in the Security app ', () => { + before(() => { + loginAndWaitForPage(KIBANA_HOME); + }); + beforeEach(() => { + openKibanaNavigation(); + }); + it('navigates to the Overview page', () => { + navigateFromKibanaCollapsibleTo(OVERVIEW_PAGE); + cy.url().should('include', OVERVIEW_URL); + }); + + it('navigates to the Alerts page', () => { + navigateFromKibanaCollapsibleTo(ALERTS_PAGE); + cy.url().should('include', ALERTS_URL); + }); + + it('navigates to the Hosts page', () => { + navigateFromKibanaCollapsibleTo(HOSTS_PAGE); + cy.url().should('include', HOSTS_URL); + }); + + it('navigates to the Network page', () => { + navigateFromKibanaCollapsibleTo(NETWORK_PAGE); + cy.url().should('include', NETWORK_URL); + }); + + it('navigates to the Timelines page', () => { + navigateFromKibanaCollapsibleTo(TIMELINES_PAGE); + cy.url().should('include', TIMELINES_URL); + }); + + it('navigates to the Cases page', () => { + navigateFromKibanaCollapsibleTo(CASES_PAGE); + cy.url().should('include', CASES_URL); + }); + + it('navigates to the Management page', () => { + navigateFromKibanaCollapsibleTo(MANAGEMENT_PAGE); + cy.url().should('include', MANAGEMENT_URL); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index 284deb67e1386..b799d487acd08 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -9,12 +9,12 @@ import { HOST_STATS, NETWORK_STATS } from '../screens/overview'; import { expandHostStats, expandNetworkStats } from '../tasks/overview'; import { loginAndWaitForPage } from '../tasks/login'; -import { OVERVIEW_PAGE } from '../urls/navigation'; +import { OVERVIEW_URL } from '../urls/navigation'; describe('Overview Page', () => { before(() => { cy.stubSecurityApi('overview'); - loginAndWaitForPage(OVERVIEW_PAGE); + loginAndWaitForPage(OVERVIEW_URL); }); it('Host stats render with correct values', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts index 6428a855c8485..10759cc7de6e9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts @@ -9,13 +9,13 @@ import { openAddFilterPopover, fillAddFilterForm } from '../tasks/search_bar'; import { GLOBAL_SEARCH_BAR_FILTER_ITEM } from '../screens/search_bar'; import { hostIpFilter } from '../objects/filter'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; // FAILING: https://github.com/elastic/kibana/issues/69595 describe.skip('SearchBar', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts index 33394760c4da9..df0a26f3649c0 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts @@ -22,11 +22,11 @@ import { loginAndWaitForPage } from '../tasks/login'; import { openTimeline } from '../tasks/security_main'; import { createNewTimeline } from '../tasks/timeline'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; describe('timeline data providers', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts index e462d6ade5dc4..87639f41d4109 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts @@ -11,11 +11,11 @@ import { loginAndWaitForPage } from '../tasks/login'; import { openTimeline, openTimelineIfClosed } from '../tasks/security_main'; import { createNewTimeline } from '../tasks/timeline'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; describe('timeline flyout button', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts index a4352f58e6fc7..383ebe2220585 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts @@ -6,7 +6,7 @@ import { reload } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; import { openEvents } from '../tasks/hosts/main'; import { DRAGGABLE_HEADER } from '../screens/timeline'; import { TABLE_COLUMN_EVENTS_MESSAGE } from '../screens/hosts/external_events'; @@ -15,7 +15,7 @@ import { removeColumn, resetFields } from '../tasks/timeline'; describe('persistent timeline', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts index 00994f7a87a7b..a2e2a72a17946 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts @@ -10,11 +10,11 @@ import { loginAndWaitForPage } from '../tasks/login'; import { openTimeline } from '../tasks/security_main'; import { executeTimelineKQL } from '../tasks/timeline'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; describe('timeline search or filter KQL bar', () => { beforeEach(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); }); it('executes a KQL query', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts index 841d41782b350..12e6f3db9b61e 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts @@ -22,11 +22,11 @@ import { uncheckTimestampToggleField, } from '../tasks/timeline'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; describe('toggle column in timeline', () => { before(() => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); }); beforeEach(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts index 1cefa7fe73d35..53460d1dfcbc6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts @@ -38,7 +38,7 @@ import { executeTimelineKQL, } from '../tasks/timeline'; -import { HOSTS_PAGE } from '../urls/navigation'; +import { HOSTS_URL } from '../urls/navigation'; import { ABSOLUTE_DATE_RANGE } from '../urls/state'; const ABSOLUTE_DATE = { @@ -235,7 +235,7 @@ describe('url state', () => { }); it.skip('sets and reads the url state for timeline by id', () => { - loginAndWaitForPage(HOSTS_PAGE); + loginAndWaitForPage(HOSTS_URL); openTimeline(); executeTimelineKQL('host.name: *'); diff --git a/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts b/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts new file mode 100644 index 0000000000000..2f7956ce370bc --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.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. + */ + +export const ALERTS_PAGE = '[data-test-subj="collapsibleNavGroup-security"] [title="Alerts"]'; + +export const CASES_PAGE = '[data-test-subj="collapsibleNavGroup-security"] [title="Cases"]'; + +export const HOSTS_PAGE = '[data-test-subj="collapsibleNavGroup-security"] [title="Hosts"]'; + +export const KIBANA_NAVIGATION_TOGGLE = '[data-test-subj="toggleNavButton"]'; + +export const MANAGEMENT_PAGE = + '[data-test-subj="collapsibleNavGroup-security"] [title="Management"]'; + +export const NETWORK_PAGE = '[data-test-subj="collapsibleNavGroup-security"] [title="Network"]'; + +export const OVERVIEW_PAGE = '[data-test-subj="collapsibleNavGroup-security"] [title="Overview"]'; + +export const TIMELINES_PAGE = '[data-test-subj="collapsibleNavGroup-security"] [title="Timelines"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/kibana_navigation.ts b/x-pack/plugins/security_solution/cypress/tasks/kibana_navigation.ts new file mode 100644 index 0000000000000..2d5b5d0de39d2 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/kibana_navigation.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KIBANA_NAVIGATION_TOGGLE } from '../screens/kibana_navigation'; + +export const navigateFromKibanaCollapsibleTo = (page: string) => { + cy.get(page).click(); +}; + +export const openKibanaNavigation = () => { + cy.get(KIBANA_NAVIGATION_TOGGLE).click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts index 7978aebfb413b..9da9abf388e4d 100644 --- a/x-pack/plugins/security_solution/cypress/urls/navigation.ts +++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts @@ -5,9 +5,9 @@ */ export const ALERTS_URL = 'app/security/alerts'; -export const CASES = '/app/security/cases'; +export const CASES_URL = '/app/security/cases'; export const DETECTIONS = '/app/siem#/detections'; -export const HOSTS_PAGE = '/app/security/hosts/allHosts'; +export const HOSTS_URL = '/app/security/hosts/allHosts'; export const HOSTS_PAGE_TAB_URLS = { allHosts: '/app/security/hosts/allHosts', anomalies: '/app/security/hosts/anomalies', @@ -15,6 +15,8 @@ export const HOSTS_PAGE_TAB_URLS = { events: '/app/security/hosts/events', uncommonProcesses: '/app/security/hosts/uncommonProcesses', }; -export const NETWORK_PAGE = '/app/security/network'; -export const OVERVIEW_PAGE = '/app/security/overview'; -export const TIMELINES_PAGE = '/app/security/timelines'; +export const KIBANA_HOME = '/app/home#/'; +export const MANAGEMENT_URL = '/app/security/management'; +export const NETWORK_URL = '/app/security/network'; +export const OVERVIEW_URL = '/app/security/overview'; +export const TIMELINES_URL = '/app/security/timelines'; From 200957bb6383d2b0344510d24f06ffdd9d66e08f Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Tue, 23 Jun 2020 14:45:47 -0600 Subject: [PATCH 21/36] Add plugin API for customizing the logging configuration (#68704) --- ...a-plugin-core-server.appenderconfigtype.md | 12 + ...na-plugin-core-server.coresetup.logging.md | 13 + .../kibana-plugin-core-server.coresetup.md | 1 + ...ana-plugin-core-server.loggerconfigtype.md | 12 + ...rver.loggercontextconfiginput.appenders.md | 11 + ...server.loggercontextconfiginput.loggers.md | 11 + ...in-core-server.loggercontextconfiginput.md | 20 + ...re-server.loggingservicesetup.configure.md | 42 +++ ...-plugin-core-server.loggingservicesetup.md | 20 + .../core/server/kibana-plugin-core-server.md | 4 + .../capabilities_service.test.ts | 4 +- src/core/server/config/config_service.test.ts | 8 +- .../config_deprecation.test.mocks.ts | 10 +- .../config_deprecation.test.ts | 8 +- src/core/server/core_context.mock.ts | 8 +- .../elasticsearch/cluster_client.test.ts | 4 +- .../elasticsearch_client_config.test.ts | 8 +- .../elasticsearch_service.test.ts | 4 +- .../elasticsearch/retry_call_cluster.test.ts | 8 +- .../version_check/ensure_es_version.test.ts | 4 +- .../http/cookie_session_storage.test.ts | 10 +- src/core/server/http/http_server.test.ts | 10 +- src/core/server/http/http_service.test.ts | 6 +- src/core/server/http/http_tools.test.ts | 4 +- .../server/http/https_redirect_server.test.ts | 4 +- .../http/integration_tests/lifecycle.test.ts | 30 +- .../http/integration_tests/request.test.ts | 6 +- .../http/integration_tests/router.test.ts | 30 +- src/core/server/http/router/router.test.ts | 4 +- src/core/server/http/test_utils.ts | 4 +- src/core/server/index.ts | 25 +- src/core/server/internal_types.ts | 2 + src/core/server/legacy/legacy_service.test.ts | 14 +- src/core/server/legacy/legacy_service.ts | 3 + ...st.ts.snap => logging_system.test.ts.snap} | 0 .../server/logging/appenders/appenders.ts | 10 +- src/core/server/logging/index.ts | 19 +- .../server/logging/logging_config.test.ts | 124 +++++++ src/core/server/logging/logging_config.ts | 68 +++- .../server/logging/logging_service.mock.ts | 80 ++-- .../server/logging/logging_service.test.ts | 244 ++++-------- src/core/server/logging/logging_service.ts | 160 ++++---- .../server/logging/logging_system.mock.ts | 84 +++++ .../server/logging/logging_system.test.ts | 348 ++++++++++++++++++ src/core/server/logging/logging_system.ts | 185 ++++++++++ src/core/server/mocks.ts | 7 +- .../discovery/plugin_manifest_parser.test.ts | 8 +- .../discovery/plugins_discovery.test.ts | 8 +- .../integration_tests/plugins_service.test.ts | 4 +- src/core/server/plugins/plugin.test.ts | 4 +- src/core/server/plugins/plugin.ts | 6 - .../server/plugins/plugin_context.test.ts | 4 +- src/core/server/plugins/plugin_context.ts | 3 + .../server/plugins/plugins_service.test.ts | 12 +- .../server/plugins/plugins_system.test.ts | 4 +- src/core/server/root/index.test.mocks.ts | 8 +- src/core/server/root/index.ts | 14 +- .../migrations/core/document_migrator.test.ts | 10 +- .../migrations/core/index_migrator.test.ts | 4 +- .../migrations/kibana/kibana_migrator.test.ts | 4 +- .../log_legacy_import.test.ts | 8 +- src/core/server/server.api.md | 91 +++++ src/core/server/server.test.mocks.ts | 6 + src/core/server/server.test.ts | 11 +- src/core/server/server.ts | 18 +- .../create_or_upgrade_saved_config.test.ts | 8 +- .../create_or_upgrade.test.ts | 4 +- .../ui_settings/ui_settings_client.test.ts | 10 +- src/core/server/uuid/resolve_uuid.test.ts | 4 +- src/core/server/uuid/uuid_service.test.ts | 6 +- .../routes/lib/short_url_lookup.test.ts | 4 +- .../server/collector/collector_set.test.ts | 4 +- src/plugins/usage_collection/server/mocks.ts | 4 +- .../plugins/core_logging/kibana.json | 7 + .../plugins/core_logging/server/.gitignore | 1 + .../plugins/core_logging/server/index.ts | 23 ++ .../plugins/core_logging/server/plugin.ts | 118 ++++++ .../plugins/core_logging/tsconfig.json | 13 + .../test_suites/core_plugins/index.ts | 1 + .../test_suites/core_plugins/logging.ts | 146 ++++++++ .../server/builtin_action_types/index.test.ts | 4 +- .../lib/send_email.test.ts | 4 +- .../server/lib/action_executor.test.ts | 6 +- .../server/lib/task_runner_factory.test.ts | 6 +- .../index_threshold/alert_type.test.ts | 4 +- .../lib/time_series_query.test.ts | 4 +- .../alerts/server/alerts_client.test.ts | 4 +- .../server/alerts_client_factory.test.ts | 4 +- .../create_execution_handler.test.ts | 4 +- .../server/task_runner/task_runner.test.ts | 4 +- .../task_runner/task_runner_factory.test.ts | 4 +- .../routes/custom_elements/create.test.ts | 4 +- .../routes/custom_elements/delete.test.ts | 4 +- .../routes/custom_elements/find.test.ts | 4 +- .../server/routes/custom_elements/get.test.ts | 4 +- .../routes/custom_elements/update.test.ts | 4 +- .../server/routes/es_fields/es_fields.test.ts | 4 +- .../server/routes/shareables/download.test.ts | 4 +- .../server/routes/shareables/zip.test.ts | 4 +- .../server/routes/workpad/create.test.ts | 4 +- .../server/routes/workpad/delete.test.ts | 4 +- .../canvas/server/routes/workpad/find.test.ts | 4 +- .../canvas/server/routes/workpad/get.test.ts | 4 +- .../server/routes/workpad/update.test.ts | 6 +- .../routes/api/__fixtures__/mock_router.ts | 4 +- .../server/config.test.ts | 6 +- .../encrypted_saved_objects_service.test.ts | 8 +- .../server/es/cluster_client_adapter.test.ts | 4 +- .../event_log/server/es/context.mock.ts | 4 +- .../event_log/server/es/context.test.ts | 4 +- .../server/event_log_service.test.ts | 4 +- .../event_log/server/event_logger.test.ts | 12 +- .../server/lib/bounded_queue.test.ts | 4 +- .../plugins/licensing/server/plugin.test.ts | 4 +- .../common/lib/screenshots/observable.test.ts | 4 +- .../server/audit/audit_service.test.ts | 14 +- .../server/authentication/api_keys.test.ts | 4 +- .../authentication/authenticator.test.ts | 6 +- .../server/authentication/index.test.ts | 8 +- .../authentication/providers/base.mock.ts | 4 +- .../server/authentication/tokens.test.ts | 4 +- .../authorization/api_authorization.test.ts | 10 +- .../authorization/app_authorization.test.ts | 12 +- .../authorization_service.test.ts | 8 +- .../disable_ui_capabilities.test.ts | 20 +- .../register_privileges_with_cluster.test.ts | 4 +- x-pack/plugins/security/server/config.test.ts | 30 +- .../security/server/routes/index.mock.ts | 6 +- .../endpoint/alerts/handlers/alerts.test.ts | 4 +- .../endpoint/routes/metadata/metadata.test.ts | 4 +- .../routes/metadata/query_builders.test.ts | 6 +- .../endpoint/routes/policy/handlers.test.ts | 6 +- .../rules_notification_alert_type.test.ts | 6 +- .../notifications/types.test.ts | 4 +- .../signals/__mocks__/es_results.ts | 4 +- .../signals/signal_rule_alert_type.test.ts | 6 +- .../capabilities_switcher.test.ts | 4 +- .../create_default_space.test.ts | 4 +- .../default_space_service.test.ts | 4 +- .../on_post_auth_interceptor.test.ts | 4 +- .../spaces_tutorial_context_factory.test.ts | 4 +- .../routes/api/external/copy_to_space.test.ts | 4 +- .../server/routes/api/external/delete.test.ts | 4 +- .../server/routes/api/external/get.test.ts | 4 +- .../routes/api/external/get_all.test.ts | 4 +- .../server/routes/api/external/post.test.ts | 4 +- .../server/routes/api/external/put.test.ts | 4 +- .../spaces_service/spaces_service.test.ts | 4 +- .../lib/reindexing/reindex_service.test.ts | 4 +- 149 files changed, 1963 insertions(+), 692 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.coresetup.logging.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.loggerconfigtype.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.appenders.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.loggers.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.loggingservicesetup.configure.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.loggingservicesetup.md rename src/core/server/logging/__snapshots__/{logging_service.test.ts.snap => logging_system.test.ts.snap} (100%) create mode 100644 src/core/server/logging/logging_system.mock.ts create mode 100644 src/core/server/logging/logging_system.test.ts create mode 100644 src/core/server/logging/logging_system.ts create mode 100644 test/plugin_functional/plugins/core_logging/kibana.json create mode 100644 test/plugin_functional/plugins/core_logging/server/.gitignore create mode 100644 test/plugin_functional/plugins/core_logging/server/index.ts create mode 100644 test/plugin_functional/plugins/core_logging/server/plugin.ts create mode 100644 test/plugin_functional/plugins/core_logging/tsconfig.json create mode 100644 test/plugin_functional/test_suites/core_plugins/logging.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md b/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md new file mode 100644 index 0000000000000..9c70e658014b3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AppenderConfigType](./kibana-plugin-core-server.appenderconfigtype.md) + +## AppenderConfigType type + + +Signature: + +```typescript +export declare type AppenderConfigType = TypeOf; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.logging.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.logging.md new file mode 100644 index 0000000000000..12fe49e65d9ca --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.logging.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [logging](./kibana-plugin-core-server.coresetup.logging.md) + +## CoreSetup.logging property + +[LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) + +Signature: + +```typescript +logging: LoggingServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index 30c054345928b..e9ed5b830b691 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -21,6 +21,7 @@ export interface CoreSetupElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | | [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup & {
resources: HttpResources;
} | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | +| [logging](./kibana-plugin-core-server.coresetup.logging.md) | LoggingServiceSetup | [LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) | | [metrics](./kibana-plugin-core-server.coresetup.metrics.md) | MetricsServiceSetup | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | | [savedObjects](./kibana-plugin-core-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) | | [status](./kibana-plugin-core-server.coresetup.status.md) | StatusServiceSetup | [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.loggerconfigtype.md b/docs/development/core/server/kibana-plugin-core-server.loggerconfigtype.md new file mode 100644 index 0000000000000..c389b7e627995 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.loggerconfigtype.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LoggerConfigType](./kibana-plugin-core-server.loggerconfigtype.md) + +## LoggerConfigType type + + +Signature: + +```typescript +export declare type LoggerConfigType = TypeOf; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.appenders.md b/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.appenders.md new file mode 100644 index 0000000000000..486a5543473ea --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.appenders.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LoggerContextConfigInput](./kibana-plugin-core-server.loggercontextconfiginput.md) > [appenders](./kibana-plugin-core-server.loggercontextconfiginput.appenders.md) + +## LoggerContextConfigInput.appenders property + +Signature: + +```typescript +appenders?: Record | Map; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.loggers.md b/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.loggers.md new file mode 100644 index 0000000000000..64d31f7d55045 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.loggers.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LoggerContextConfigInput](./kibana-plugin-core-server.loggercontextconfiginput.md) > [loggers](./kibana-plugin-core-server.loggercontextconfiginput.loggers.md) + +## LoggerContextConfigInput.loggers property + +Signature: + +```typescript +loggers?: LoggerConfigType[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.md b/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.md new file mode 100644 index 0000000000000..fb6922d839cb8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.loggercontextconfiginput.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LoggerContextConfigInput](./kibana-plugin-core-server.loggercontextconfiginput.md) + +## LoggerContextConfigInput interface + + +Signature: + +```typescript +export interface LoggerContextConfigInput +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appenders](./kibana-plugin-core-server.loggercontextconfiginput.appenders.md) | Record<string, AppenderConfigType> | Map<string, AppenderConfigType> | | +| [loggers](./kibana-plugin-core-server.loggercontextconfiginput.loggers.md) | LoggerConfigType[] | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.loggingservicesetup.configure.md b/docs/development/core/server/kibana-plugin-core-server.loggingservicesetup.configure.md new file mode 100644 index 0000000000000..04a3cf9aff644 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.loggingservicesetup.configure.md @@ -0,0 +1,42 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) > [configure](./kibana-plugin-core-server.loggingservicesetup.configure.md) + +## LoggingServiceSetup.configure() method + +Customizes the logging config for the plugin's context. + +Signature: + +```typescript +configure(config$: Observable): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| config$ | Observable<LoggerContextConfigInput> | | + +Returns: + +`void` + +## Remarks + +Assumes that that the `context` property of the individual `logger` items emitted by `config$` are relative to the plugin's logging context (defaults to `plugins.`). + +## Example + +Customize the configuration for the plugins.data.search context. + +```ts +core.logging.configure( + of({ + appenders: new Map(), + loggers: [{ context: 'search', appenders: ['default'] }] + }) +) + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.loggingservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.loggingservicesetup.md new file mode 100644 index 0000000000000..010438ce28803 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.loggingservicesetup.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) + +## LoggingServiceSetup interface + +Provides APIs to plugins for customizing the plugin's logger. + +Signature: + +```typescript +export interface LoggingServiceSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [configure(config$)](./kibana-plugin-core-server.loggingservicesetup.configure.md) | Customizes the logging config for the plugin's context. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 0f1bbbe7176e5..1a03ac5ee3d1a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -108,7 +108,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LegacyServiceSetupDeps](./kibana-plugin-core-server.legacyservicesetupdeps.md) | | | [LegacyServiceStartDeps](./kibana-plugin-core-server.legacyservicestartdeps.md) | | | [Logger](./kibana-plugin-core-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. | +| [LoggerContextConfigInput](./kibana-plugin-core-server.loggercontextconfiginput.md) | | | [LoggerFactory](./kibana-plugin-core-server.loggerfactory.md) | The single purpose of LoggerFactory interface is to define a way to retrieve a context-based logger instance. | +| [LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) | Provides APIs to plugins for customizing the plugin's logger. | | [LogMeta](./kibana-plugin-core-server.logmeta.md) | Contextual metadata | | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | APIs to retrieves metrics gathered and exposed by the core platform. | | [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) | | @@ -209,6 +211,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Type Alias | Description | | --- | --- | +| [AppenderConfigType](./kibana-plugin-core-server.appenderconfigtype.md) | | | [AuthenticationHandler](./kibana-plugin-core-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-core-server.authtoolkit.md). | | [AuthHeaders](./kibana-plugin-core-server.authheaders.md) | Auth Headers map | | [AuthResult](./kibana-plugin-core-server.authresult.md) | | @@ -242,6 +245,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | | [KnownHeaders](./kibana-plugin-core-server.knownheaders.md) | Set of well-known HTTP headers. | | [LifecycleResponseFactory](./kibana-plugin-core-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | +| [LoggerConfigType](./kibana-plugin-core-server.loggerconfigtype.md) | | | [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-core-server.migration_assistance_index_action.md) | | | [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-core-server.migration_deprecation_level.md) | | | [MutatingOperationRefreshSetting](./kibana-plugin-core-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index 6be9846f5a86a..b4d620965b047 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -20,7 +20,7 @@ import supertest from 'supertest'; import { HttpService, InternalHttpServiceSetup } from '../../http'; import { contextServiceMock } from '../../context/context_service.mock'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { Env } from '../../config'; import { getEnvOptions } from '../../config/__mocks__/env'; import { CapabilitiesService, CapabilitiesSetup } from '..'; @@ -44,7 +44,7 @@ describe('CapabilitiesService', () => { service = new CapabilitiesService({ coreId, env, - logger: loggingServiceMock.create(), + logger: loggingSystemMock.create(), configService: {} as any, }); serviceSetup = await service.setup({ http: httpSetup }); diff --git a/src/core/server/config/config_service.test.ts b/src/core/server/config/config_service.test.ts index 5f28fca1371b0..236cf6579d7c8 100644 --- a/src/core/server/config/config_service.test.ts +++ b/src/core/server/config/config_service.test.ts @@ -28,12 +28,12 @@ import { rawConfigServiceMock } from './raw_config_service.mock'; import { schema } from '@kbn/config-schema'; import { ConfigService, Env } from '.'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { getEnvOptions } from './__mocks__/env'; const emptyArgv = getEnvOptions(); const defaultEnv = new Env('/kibana', emptyArgv); -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); const getRawConfigProvider = (rawConfig: Record) => rawConfigServiceMock.create({ rawConfig }); @@ -443,9 +443,9 @@ test('logs deprecation warning during validation', async () => { return config; }); - loggingServiceMock.clear(logger); + loggingSystemMock.clear(logger); await configService.validate(); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "some deprecation message", diff --git a/src/core/server/config/integration_tests/config_deprecation.test.mocks.ts b/src/core/server/config/integration_tests/config_deprecation.test.mocks.ts index 58b2da926b7c3..1d42c7667a34d 100644 --- a/src/core/server/config/integration_tests/config_deprecation.test.mocks.ts +++ b/src/core/server/config/integration_tests/config_deprecation.test.mocks.ts @@ -17,9 +17,9 @@ * under the License. */ -import { loggingServiceMock } from '../../logging/logging_service.mock'; -export const mockLoggingService = loggingServiceMock.create(); -mockLoggingService.asLoggerFactory.mockImplementation(() => mockLoggingService); -jest.doMock('../../logging/logging_service', () => ({ - LoggingService: jest.fn(() => mockLoggingService), +import { loggingSystemMock } from '../../logging/logging_system.mock'; +export const mockLoggingSystem = loggingSystemMock.create(); +mockLoggingSystem.asLoggerFactory.mockImplementation(() => mockLoggingSystem); +jest.doMock('../../logging/logging_system', () => ({ + LoggingSystem: jest.fn(() => mockLoggingSystem), })); diff --git a/src/core/server/config/integration_tests/config_deprecation.test.ts b/src/core/server/config/integration_tests/config_deprecation.test.ts index 3523b074ea5b4..56385f3b171c9 100644 --- a/src/core/server/config/integration_tests/config_deprecation.test.ts +++ b/src/core/server/config/integration_tests/config_deprecation.test.ts @@ -17,8 +17,8 @@ * under the License. */ -import { mockLoggingService } from './config_deprecation.test.mocks'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { mockLoggingSystem } from './config_deprecation.test.mocks'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import * as kbnTestServer from '../../../../test_utils/kbn_server'; describe('configuration deprecations', () => { @@ -35,7 +35,7 @@ describe('configuration deprecations', () => { await root.setup(); - const logs = loggingServiceMock.collect(mockLoggingService); + const logs = loggingSystemMock.collect(mockLoggingSystem); const warnings = logs.warn.flatMap((i) => i); expect(warnings).not.toContain( '"optimize.lazy" is deprecated and has been replaced by "optimize.watch"' @@ -55,7 +55,7 @@ describe('configuration deprecations', () => { await root.setup(); - const logs = loggingServiceMock.collect(mockLoggingService); + const logs = loggingSystemMock.collect(mockLoggingSystem); const warnings = logs.warn.flatMap((i) => i); expect(warnings).toContain( '"optimize.lazy" is deprecated and has been replaced by "optimize.watch"' diff --git a/src/core/server/core_context.mock.ts b/src/core/server/core_context.mock.ts index d287348e19079..f870d30528df4 100644 --- a/src/core/server/core_context.mock.ts +++ b/src/core/server/core_context.mock.ts @@ -20,17 +20,17 @@ import { CoreContext } from './core_context'; import { getEnvOptions } from './config/__mocks__/env'; import { Env, IConfigService } from './config'; -import { loggingServiceMock } from './logging/logging_service.mock'; +import { loggingSystemMock } from './logging/logging_system.mock'; import { configServiceMock } from './config/config_service.mock'; -import { ILoggingService } from './logging'; +import { ILoggingSystem } from './logging'; function create({ env = Env.createDefault(getEnvOptions()), - logger = loggingServiceMock.create(), + logger = loggingSystemMock.create(), configService = configServiceMock.create(), }: { env?: Env; - logger?: jest.Mocked; + logger?: jest.Mocked; configService?: jest.Mocked; } = {}): DeeplyMockedKeys { return { coreId: Symbol(), env, logger, configService }; diff --git a/src/core/server/elasticsearch/cluster_client.test.ts b/src/core/server/elasticsearch/cluster_client.test.ts index db277fa0e0607..820272bdf14b8 100644 --- a/src/core/server/elasticsearch/cluster_client.test.ts +++ b/src/core/server/elasticsearch/cluster_client.test.ts @@ -28,11 +28,11 @@ import { import { errors } from 'elasticsearch'; import { get } from 'lodash'; import { Logger } from '../logging'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { httpServerMock } from '../http/http_server.mocks'; import { ClusterClient } from './cluster_client'; -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); afterEach(() => jest.clearAllMocks()); test('#constructor creates client with parsed config', () => { diff --git a/src/core/server/elasticsearch/elasticsearch_client_config.test.ts b/src/core/server/elasticsearch/elasticsearch_client_config.test.ts index 20c10459e0e8a..77d1e41c9ad83 100644 --- a/src/core/server/elasticsearch/elasticsearch_client_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_client_config.test.ts @@ -18,12 +18,12 @@ */ import { duration } from 'moment'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { ElasticsearchClientConfig, parseElasticsearchClientConfig, } from './elasticsearch_client_config'; -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); afterEach(() => jest.clearAllMocks()); test('parses minimally specified config', () => { @@ -360,7 +360,7 @@ describe('#log', () => { expect(typeof esLogger.close).toBe('function'); - expect(loggingServiceMock.collect(logger)).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger)).toMatchInlineSnapshot(` Object { "debug": Array [], "error": Array [ @@ -406,7 +406,7 @@ Object { expect(typeof esLogger.close).toBe('function'); - expect(loggingServiceMock.collect(logger)).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger)).toMatchInlineSnapshot(` Object { "debug": Array [ Array [ diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 8bf0df74186a9..0a7068903e15c 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -26,7 +26,7 @@ import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { configServiceMock } from '../config/config_service.mock'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; @@ -55,7 +55,7 @@ configService.atPath.mockReturnValue( let env: Env; let coreContext: CoreContext; -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); beforeEach(() => { env = Env.createDefault(getEnvOptions()); diff --git a/src/core/server/elasticsearch/retry_call_cluster.test.ts b/src/core/server/elasticsearch/retry_call_cluster.test.ts index 8be138e6752d2..18ffa95048c4d 100644 --- a/src/core/server/elasticsearch/retry_call_cluster.test.ts +++ b/src/core/server/elasticsearch/retry_call_cluster.test.ts @@ -19,7 +19,7 @@ import * as legacyElasticsearch from 'elasticsearch'; import { retryCallCluster, migrationsRetryCallCluster } from './retry_call_cluster'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; describe('retryCallCluster', () => { it('retries ES API calls that rejects with NoConnections', () => { @@ -69,10 +69,10 @@ describe('migrationsRetryCallCluster', () => { 'Gone', ]; - const mockLogger = loggingServiceMock.create(); + const mockLogger = loggingSystemMock.create(); beforeEach(() => { - loggingServiceMock.clear(mockLogger); + loggingSystemMock.clear(mockLogger); }); errors.forEach((errorName) => { @@ -133,7 +133,7 @@ describe('migrationsRetryCallCluster', () => { callEsApi.mockResolvedValueOnce('done'); const retried = migrationsRetryCallCluster(callEsApi, mockLogger.get('mock log'), 1); await retried('endpoint'); - expect(loggingServiceMock.collect(mockLogger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(mockLogger).warn).toMatchInlineSnapshot(` Array [ Array [ "Unable to connect to Elasticsearch. Error: No Living connections", diff --git a/src/core/server/elasticsearch/version_check/ensure_es_version.test.ts b/src/core/server/elasticsearch/version_check/ensure_es_version.test.ts index a2090ed111ca1..3d1218d4a8e8b 100644 --- a/src/core/server/elasticsearch/version_check/ensure_es_version.test.ts +++ b/src/core/server/elasticsearch/version_check/ensure_es_version.test.ts @@ -17,12 +17,12 @@ * under the License. */ import { mapNodesVersionCompatibility, pollEsNodesVersion, NodesInfo } from './ensure_es_version'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { take, delay } from 'rxjs/operators'; import { TestScheduler } from 'rxjs/testing'; import { of } from 'rxjs'; -const mockLoggerFactory = loggingServiceMock.create(); +const mockLoggerFactory = loggingSystemMock.create(); const mockLogger = mockLoggerFactory.get('mock logger'); const KIBANA_VERSION = '5.1.0'; diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index 3afe5e0c4dfc7..1fb2b5693bb61 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -29,14 +29,14 @@ import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; import { configServiceMock } from '../config/config_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { httpServerMock } from './http_server.mocks'; import { createCookieSessionStorageFactory } from './cookie_session_storage'; let server: HttpService; -let logger: ReturnType; +let logger: ReturnType; let env: Env; let coreContext: CoreContext; const configService = configServiceMock.create(); @@ -67,7 +67,7 @@ configService.atPath.mockReturnValue( ); beforeEach(() => { - logger = loggingServiceMock.create(); + logger = loggingSystemMock.create(); env = Env.createDefault(getEnvOptions()); coreContext = { coreId: Symbol(), env, logger, configService: configService as any }; @@ -324,7 +324,7 @@ describe('Cookie based SessionStorage', () => { expect(mockServer.auth.test).toBeCalledTimes(1); expect(mockServer.auth.test).toHaveBeenCalledWith('security-cookie', mockRequest); - expect(loggingServiceMock.collect(logger).warn).toEqual([ + expect(loggingSystemMock.collect(logger).warn).toEqual([ ['Found 2 auth sessions when we were only expecting 1.'], ]); }); @@ -381,7 +381,7 @@ describe('Cookie based SessionStorage', () => { const session = await factory.asScoped(KibanaRequest.from(mockRequest)).get(); expect(session).toBe(null); - expect(loggingServiceMock.collect(logger).debug).toEqual([['Error: Invalid cookie.']]); + expect(loggingSystemMock.collect(logger).debug).toEqual([['Error: Invalid cookie.']]); }); }); diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 9a5deb9b45562..4520851bb460c 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -31,7 +31,7 @@ import { RouteValidationResultFactory, RouteValidationFunction, } from './router'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { HttpServer } from './http_server'; import { Readable } from 'stream'; import { RequestHandlerContext } from 'kibana/server'; @@ -48,7 +48,7 @@ let server: HttpServer; let config: HttpConfig; let configWithSSL: HttpConfig; -const loggingService = loggingServiceMock.create(); +const loggingService = loggingSystemMock.create(); const logger = loggingService.get(); const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); @@ -97,7 +97,7 @@ test('log listening address after started', async () => { await server.start(); expect(server.isListening()).toBe(true); - expect(loggingServiceMock.collect(loggingService).info).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(loggingService).info).toMatchInlineSnapshot(` Array [ Array [ "http server running at http://127.0.0.1:10002", @@ -113,7 +113,7 @@ test('log listening address after started when configured with BasePath and rewr await server.start(); expect(server.isListening()).toBe(true); - expect(loggingServiceMock.collect(loggingService).info).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(loggingService).info).toMatchInlineSnapshot(` Array [ Array [ "http server running at http://127.0.0.1:10002", @@ -129,7 +129,7 @@ test('log listening address after started when configured with BasePath and rewr await server.start(); expect(server.isListening()).toBe(true); - expect(loggingServiceMock.collect(loggingService).info).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(loggingService).info).toMatchInlineSnapshot(` Array [ Array [ "http server running at http://127.0.0.1:10002/bar", diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index 8b500caf217dc..3d759b427d9fb 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -25,12 +25,12 @@ import { HttpService } from '.'; import { HttpConfigType, config } from './http_config'; import { httpServerMock } from './http_server.mocks'; import { ConfigService, Env } from '../config'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { contextServiceMock } from '../context/context_service.mock'; import { getEnvOptions } from '../config/__mocks__/env'; import { config as cspConfig } from '../csp'; -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); const env = Env.createDefault(getEnvOptions()); const coreId = Symbol(); @@ -159,7 +159,7 @@ test('logs error if already set up', async () => { await service.setup(setupDeps); - expect(loggingServiceMock.collect(logger).warn).toMatchSnapshot(); + expect(loggingSystemMock.collect(logger).warn).toMatchSnapshot(); }); test('stops http server', async () => { diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index 7d5a7277a767a..f09d862f9edac 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -34,7 +34,7 @@ import { defaultValidationErrorHandler, HapiValidationError, getServerOptions } import { HttpServer } from './http_server'; import { HttpConfig, config } from './http_config'; import { Router } from './router'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { ByteSizeValue } from '@kbn/config-schema'; const emptyOutput = { @@ -77,7 +77,7 @@ describe('defaultValidationErrorHandler', () => { }); describe('timeouts', () => { - const logger = loggingServiceMock.create(); + const logger = loggingSystemMock.create(); const server = new HttpServer(logger, 'foo'); const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); diff --git a/src/core/server/http/https_redirect_server.test.ts b/src/core/server/http/https_redirect_server.test.ts index a7d3cbe41aa3d..f35456f01c19b 100644 --- a/src/core/server/http/https_redirect_server.test.ts +++ b/src/core/server/http/https_redirect_server.test.ts @@ -27,7 +27,7 @@ import supertest from 'supertest'; import { ByteSizeValue } from '@kbn/config-schema'; import { HttpConfig } from '.'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { HttpsRedirectServer } from './https_redirect_server'; const chance = new Chance(); @@ -50,7 +50,7 @@ beforeEach(() => { }, } as HttpConfig; - server = new HttpsRedirectServer(loggingServiceMock.create().get()); + server = new HttpsRedirectServer(loggingSystemMock.create().get()); }); afterEach(async () => { diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 73ed4e5de4b04..879cbc689f8e7 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -24,12 +24,12 @@ import { ensureRawRequest } from '../router'; import { HttpService } from '../http_service'; import { contextServiceMock } from '../../context/context_service.mock'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { createHttpServer } from '../test_utils'; let server: HttpService; -let logger: ReturnType; +let logger: ReturnType; const contextSetup = contextServiceMock.createSetupContract(); @@ -38,7 +38,7 @@ const setupDeps = { }; beforeEach(() => { - logger = loggingServiceMock.create(); + logger = loggingSystemMock.create(); server = createHttpServer({ logger }); }); @@ -167,7 +167,7 @@ describe('OnPreAuth', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: reason], @@ -188,7 +188,7 @@ describe('OnPreAuth', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Unexpected result from OnPreAuth. Expected OnPreAuthResult or KibanaResponse, but given: [object Object].], @@ -301,7 +301,7 @@ describe('OnPostAuth', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: reason], @@ -321,7 +321,7 @@ describe('OnPostAuth', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Unexpected result from OnPostAuth. Expected OnPostAuthResult or KibanaResponse, but given: [object Object].], @@ -506,7 +506,7 @@ describe('Auth', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: reason], @@ -703,7 +703,7 @@ describe('Auth', () => { const response = await supertest(innerServer.listener).get('/').expect(200); expect(response.header['www-authenticate']).toBe('from auth interceptor'); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "onPreResponseHandler rewrote a response header [www-authenticate].", @@ -736,7 +736,7 @@ describe('Auth', () => { const response = await supertest(innerServer.listener).get('/').expect(400); expect(response.header['www-authenticate']).toBe('from auth interceptor'); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "onPreResponseHandler rewrote a response header [www-authenticate].", @@ -798,7 +798,7 @@ describe('Auth', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: reason], @@ -818,7 +818,7 @@ describe('Auth', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Unexpected result from OnPostAuth. Expected OnPostAuthResult or KibanaResponse, but given: [object Object].], @@ -929,7 +929,7 @@ describe('OnPreResponse', () => { await supertest(innerServer.listener).get('/').expect(200); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "onPreResponseHandler rewrote a response header [x-kibana-header].", @@ -953,7 +953,7 @@ describe('OnPreResponse', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: reason], @@ -975,7 +975,7 @@ describe('OnPreResponse', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Unexpected result from OnPreResponse. Expected OnPreResponseResult, but given: [object Object].], diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts index d33757273042b..2d018f7f464b5 100644 --- a/src/core/server/http/integration_tests/request.test.ts +++ b/src/core/server/http/integration_tests/request.test.ts @@ -21,12 +21,12 @@ import supertest from 'supertest'; import { HttpService } from '../http_service'; import { contextServiceMock } from '../../context/context_service.mock'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { createHttpServer } from '../test_utils'; let server: HttpService; -let logger: ReturnType; +let logger: ReturnType; const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { @@ -34,7 +34,7 @@ const setupDeps = { }; beforeEach(() => { - logger = loggingServiceMock.create(); + logger = loggingSystemMock.create(); server = createHttpServer({ logger }); }); diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index 8f3799b12eccb..bb36fefa96611 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -24,12 +24,12 @@ import { schema } from '@kbn/config-schema'; import { HttpService } from '../http_service'; import { contextServiceMock } from '../../context/context_service.mock'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { createHttpServer } from '../test_utils'; let server: HttpService; -let logger: ReturnType; +let logger: ReturnType; const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { @@ -37,7 +37,7 @@ const setupDeps = { }; beforeEach(() => { - logger = loggingServiceMock.create(); + logger = loggingSystemMock.create(); server = createHttpServer({ logger }); }); @@ -347,7 +347,7 @@ describe('Handler', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: unexpected error], @@ -368,7 +368,7 @@ describe('Handler', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Unauthorized], @@ -387,7 +387,7 @@ describe('Handler', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Unexpected result from Route Handler. Expected KibanaResponse, but given: string.], @@ -763,7 +763,7 @@ describe('Response factory', () => { await supertest(innerServer.listener).get('/').expect(500); // error happens within hapi when route handler already finished execution. - expect(loggingServiceMock.collect(logger).error).toHaveLength(0); + expect(loggingSystemMock.collect(logger).error).toHaveLength(0); }); it('200 OK with body', async () => { @@ -855,7 +855,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: expected 'location' header to be set], @@ -1261,7 +1261,7 @@ describe('Response factory', () => { message: 'An internal server error occurred.', statusCode: 500, }); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Unexpected Http status code. Expected from 400 to 599, but given: 200], @@ -1330,7 +1330,7 @@ describe('Response factory', () => { await supertest(innerServer.listener).get('/').expect(500); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: expected 'location' header to be set], @@ -1445,7 +1445,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('reason'); - expect(loggingServiceMock.collect(logger).error).toHaveLength(0); + expect(loggingSystemMock.collect(logger).error).toHaveLength(0); }); it('throws an error if not valid error is provided', async () => { @@ -1464,7 +1464,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: expected error message to be provided], @@ -1488,7 +1488,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: expected error message to be provided], @@ -1511,7 +1511,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: options.statusCode is expected to be set. given options: undefined], @@ -1534,7 +1534,7 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(500); expect(result.body.message).toBe('An internal server error occurred.'); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Unexpected Http status code. Expected from 100 to 599, but given: 20.], diff --git a/src/core/server/http/router/router.test.ts b/src/core/server/http/router/router.test.ts index 9655e2153b863..fa38c7bd6b336 100644 --- a/src/core/server/http/router/router.test.ts +++ b/src/core/server/http/router/router.test.ts @@ -18,10 +18,10 @@ */ import { Router } from './router'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { schema } from '@kbn/config-schema'; -const logger = loggingServiceMock.create().get(); +const logger = loggingSystemMock.create().get(); const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); describe('Router', () => { diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index 0e639aa72a825..bda66e1de8168 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -24,12 +24,12 @@ import { getEnvOptions } from '../config/__mocks__/env'; import { HttpService } from './http_service'; import { CoreContext } from '../core_context'; import { configServiceMock } from '../config/config_service.mock'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; const coreId = Symbol('core'); const env = Env.createDefault(getEnvOptions()); -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); const configService = configServiceMock.create(); configService.atPath.mockReturnValue( diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 0da7e5d66cf2a..e0afd5e57f041 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -62,6 +62,12 @@ import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { UuidServiceSetup } from './uuid'; import { MetricsServiceSetup } from './metrics'; import { StatusServiceSetup } from './status'; +import { + LoggingServiceSetup, + appendersSchema, + loggerContextConfigSchema, + loggerSchema, +} from './logging'; export { bootstrap } from './bootstrap'; export { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './capabilities'; @@ -187,7 +193,17 @@ export { } from './http_resources'; export { IRenderOptions } from './rendering'; -export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; +export { + Logger, + LoggerFactory, + LogMeta, + LogRecord, + LogLevel, + LoggingServiceSetup, + LoggerContextConfigInput, + LoggerConfigType, + AppenderConfigType, +} from './logging'; export { DiscoveredPlugin, @@ -385,6 +401,8 @@ export interface CoreSetup = KbnServer as any; @@ -64,7 +65,7 @@ let setupDeps: LegacyServiceSetupDeps; let startDeps: LegacyServiceStartDeps; -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); let configService: ReturnType; let uuidSetup: ReturnType; @@ -100,6 +101,7 @@ beforeEach(() => { metrics: metricsServiceMock.createInternalSetupContract(), uuid: uuidSetup, status: statusServiceMock.createInternalSetupContract(), + logging: loggingServiceMock.createInternalSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, uiPlugins: { @@ -281,7 +283,7 @@ describe('once LegacyService is set up with connection info', () => { const [mockKbnServer] = MockKbnServer.mock.instances as Array>; expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled(); - expect(loggingServiceMock.collect(logger).error).toEqual([]); + expect(loggingSystemMock.collect(logger).error).toEqual([]); const configError = new Error('something went wrong'); mockKbnServer.applyLoggingConfiguration.mockImplementation(() => { @@ -290,7 +292,7 @@ describe('once LegacyService is set up with connection info', () => { config$.next(new ObjectToConfigAdapter({ logging: { verbose: true } })); - expect(loggingServiceMock.collect(logger).error).toEqual([[configError]]); + expect(loggingSystemMock.collect(logger).error).toEqual([[configError]]); }); test('logs error if config service fails.', async () => { @@ -306,13 +308,13 @@ describe('once LegacyService is set up with connection info', () => { const [mockKbnServer] = MockKbnServer.mock.instances; expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled(); - expect(loggingServiceMock.collect(logger).error).toEqual([]); + expect(loggingSystemMock.collect(logger).error).toEqual([]); const configError = new Error('something went wrong'); config$.error(configError); expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled(); - expect(loggingServiceMock.collect(logger).error).toEqual([[configError]]); + expect(loggingSystemMock.collect(logger).error).toEqual([[configError]]); }); }); diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index cfc53b10d91f0..be737f6593c02 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -309,6 +309,9 @@ export class LegacyService implements CoreService { csp: setupDeps.core.http.csp, getServerInfo: setupDeps.core.http.getServerInfo, }, + logging: { + configure: (config$) => setupDeps.core.logging.configure([], config$), + }, metrics: { getOpsMetrics$: setupDeps.core.metrics.getOpsMetrics$, }, diff --git a/src/core/server/logging/__snapshots__/logging_service.test.ts.snap b/src/core/server/logging/__snapshots__/logging_system.test.ts.snap similarity index 100% rename from src/core/server/logging/__snapshots__/logging_service.test.ts.snap rename to src/core/server/logging/__snapshots__/logging_system.test.ts.snap diff --git a/src/core/server/logging/appenders/appenders.ts b/src/core/server/logging/appenders/appenders.ts index 3aa86495e4d82..3b90a10a1a76c 100644 --- a/src/core/server/logging/appenders/appenders.ts +++ b/src/core/server/logging/appenders/appenders.ts @@ -26,13 +26,19 @@ import { LogRecord } from '../log_record'; import { ConsoleAppender } from './console/console_appender'; import { FileAppender } from './file/file_appender'; -const appendersSchema = schema.oneOf([ +/** + * Config schema for validting the shape of the `appenders` key in in {@link LoggerContextConfigType} or + * {@link LoggingConfigType}. + * + * @public + */ +export const appendersSchema = schema.oneOf([ ConsoleAppender.configSchema, FileAppender.configSchema, LegacyAppender.configSchema, ]); -/** @internal */ +/** @public */ export type AppenderConfigType = TypeOf; /** diff --git a/src/core/server/logging/index.ts b/src/core/server/logging/index.ts index fd35ed39092b3..9471972030281 100644 --- a/src/core/server/logging/index.ts +++ b/src/core/server/logging/index.ts @@ -21,7 +21,18 @@ export { Logger, LogMeta } from './logger'; export { LoggerFactory } from './logger_factory'; export { LogRecord } from './log_record'; export { LogLevel } from './log_level'; -/** @internal */ -export { config, LoggingConfigType } from './logging_config'; -/** @internal */ -export { LoggingService, ILoggingService } from './logging_service'; +export { + config, + LoggingConfigType, + LoggerContextConfigInput, + LoggerConfigType, + loggerContextConfigSchema, + loggerSchema, +} from './logging_config'; +export { LoggingSystem, ILoggingSystem } from './logging_system'; +export { + InternalLoggingServiceSetup, + LoggingServiceSetup, + LoggingService, +} from './logging_service'; +export { appendersSchema, AppenderConfigType } from './appenders/appenders'; diff --git a/src/core/server/logging/logging_config.test.ts b/src/core/server/logging/logging_config.test.ts index 75f571d34c25c..e2ce3e1983aa1 100644 --- a/src/core/server/logging/logging_config.test.ts +++ b/src/core/server/logging/logging_config.test.ts @@ -171,3 +171,127 @@ test('fails if loggers use unknown appenders.', () => { expect(() => new LoggingConfig(validateConfig)).toThrowErrorMatchingSnapshot(); }); + +describe('extend', () => { + it('adds new appenders', () => { + const configValue = new LoggingConfig( + config.schema.validate({ + appenders: { + file1: { + kind: 'file', + layout: { kind: 'pattern' }, + path: 'path', + }, + }, + }) + ); + + const mergedConfigValue = configValue.extend( + config.schema.validate({ + appenders: { + file2: { + kind: 'file', + layout: { kind: 'pattern' }, + path: 'path', + }, + }, + }) + ); + + expect([...mergedConfigValue.appenders.keys()]).toEqual([ + 'default', + 'console', + 'file1', + 'file2', + ]); + }); + + it('overrides appenders', () => { + const configValue = new LoggingConfig( + config.schema.validate({ + appenders: { + file1: { + kind: 'file', + layout: { kind: 'pattern' }, + path: 'path', + }, + }, + }) + ); + + const mergedConfigValue = configValue.extend( + config.schema.validate({ + appenders: { + file1: { + kind: 'file', + layout: { kind: 'json' }, + path: 'updatedPath', + }, + }, + }) + ); + + expect(mergedConfigValue.appenders.get('file1')).toEqual({ + kind: 'file', + layout: { kind: 'json' }, + path: 'updatedPath', + }); + }); + + it('adds new loggers', () => { + const configValue = new LoggingConfig( + config.schema.validate({ + loggers: [ + { + context: 'plugins', + level: 'warn', + }, + ], + }) + ); + + const mergedConfigValue = configValue.extend( + config.schema.validate({ + loggers: [ + { + context: 'plugins.pid', + level: 'trace', + }, + ], + }) + ); + + expect([...mergedConfigValue.loggers.keys()]).toEqual(['root', 'plugins', 'plugins.pid']); + }); + + it('overrides loggers', () => { + const configValue = new LoggingConfig( + config.schema.validate({ + loggers: [ + { + context: 'plugins', + level: 'warn', + }, + ], + }) + ); + + const mergedConfigValue = configValue.extend( + config.schema.validate({ + loggers: [ + { + appenders: ['console'], + context: 'plugins', + level: 'trace', + }, + ], + }) + ); + + expect(mergedConfigValue.loggers.get('plugins')).toEqual({ + appenders: ['console'], + context: 'plugins', + level: 'trace', + }); + }); +}); diff --git a/src/core/server/logging/logging_config.ts b/src/core/server/logging/logging_config.ts index 772909ce584e5..a6aafabeb970c 100644 --- a/src/core/server/logging/logging_config.ts +++ b/src/core/server/logging/logging_config.ts @@ -39,7 +39,7 @@ const ROOT_CONTEXT_NAME = 'root'; */ const DEFAULT_APPENDER_NAME = 'default'; -const createLevelSchema = schema.oneOf( +const levelSchema = schema.oneOf( [ schema.literal('all'), schema.literal('fatal'), @@ -55,21 +55,26 @@ const createLevelSchema = schema.oneOf( } ); -const createLoggerSchema = schema.object({ +/** + * Config schema for validating the `loggers` key in {@link LoggerContextConfigType} or {@link LoggingConfigType}. + * + * @public + */ +export const loggerSchema = schema.object({ appenders: schema.arrayOf(schema.string(), { defaultValue: [] }), context: schema.string(), - level: createLevelSchema, + level: levelSchema, }); -/** @internal */ -export type LoggerConfigType = TypeOf; +/** @public */ +export type LoggerConfigType = TypeOf; export const config = { path: 'logging', schema: schema.object({ appenders: schema.mapOf(schema.string(), Appenders.configSchema, { defaultValue: new Map(), }), - loggers: schema.arrayOf(createLoggerSchema, { + loggers: schema.arrayOf(loggerSchema, { defaultValue: [], }), root: schema.object( @@ -78,7 +83,7 @@ export const config = { defaultValue: [DEFAULT_APPENDER_NAME], minSize: 1, }), - level: createLevelSchema, + level: levelSchema, }, { validate(rawConfig) { @@ -93,6 +98,29 @@ export const config = { export type LoggingConfigType = TypeOf; +/** + * Config schema for validating the inputs to the {@link LoggingServiceStart.configure} API. + * See {@link LoggerContextConfigType}. + * + * @public + */ +export const loggerContextConfigSchema = schema.object({ + appenders: schema.mapOf(schema.string(), Appenders.configSchema, { + defaultValue: new Map(), + }), + + loggers: schema.arrayOf(loggerSchema, { defaultValue: [] }), +}); + +/** @public */ +export type LoggerContextConfigType = TypeOf; +/** @public */ +export interface LoggerContextConfigInput { + // config-schema knows how to handle either Maps or Records + appenders?: Record | Map; + loggers?: LoggerConfigType[]; +} + /** * Describes the config used to fully setup logging subsystem. * @internal @@ -147,11 +175,35 @@ export class LoggingConfig { */ public readonly loggers: Map = new Map(); - constructor(configType: LoggingConfigType) { + constructor(private readonly configType: LoggingConfigType) { this.fillAppendersConfig(configType); this.fillLoggersConfig(configType); } + /** + * Returns a new LoggingConfig that merges the existing config with the specified config. + * + * @remarks + * Does not support merging the `root` config property. + * + * @param contextConfig + */ + public extend(contextConfig: LoggerContextConfigType) { + // Use a Map to de-dupe any loggers for the same context. contextConfig overrides existing config. + const mergedLoggers = new Map([ + ...this.configType.loggers.map((l) => [l.context, l] as [string, LoggerConfigType]), + ...contextConfig.loggers.map((l) => [l.context, l] as [string, LoggerConfigType]), + ]); + + const mergedConfig: LoggingConfigType = { + appenders: new Map([...this.configType.appenders, ...contextConfig.appenders]), + loggers: [...mergedLoggers.values()], + root: this.configType.root, + }; + + return new LoggingConfig(mergedConfig); + } + private fillAppendersConfig(loggingConfig: LoggingConfigType) { for (const [appenderKey, appenderSchema] of loggingConfig.appenders) { this.appenders.set(appenderKey, appenderSchema); diff --git a/src/core/server/logging/logging_service.mock.ts b/src/core/server/logging/logging_service.mock.ts index 15d66c2e8535c..21edbe670eaec 100644 --- a/src/core/server/logging/logging_service.mock.ts +++ b/src/core/server/logging/logging_service.mock.ts @@ -17,67 +17,35 @@ * under the License. */ -// Test helpers to simplify mocking logs and collecting all their outputs -import { ILoggingService } from './logging_service'; -import { LoggerFactory } from './logger_factory'; -import { loggerMock, MockedLogger } from './logger.mock'; - -const createLoggingServiceMock = () => { - const mockLog = loggerMock.create(); - - mockLog.get.mockImplementation((...context) => ({ - ...mockLog, - context, - })); - - const mocked: jest.Mocked = { - get: jest.fn(), - asLoggerFactory: jest.fn(), - upgrade: jest.fn(), +import { + LoggingService, + LoggingServiceSetup, + InternalLoggingServiceSetup, +} from './logging_service'; + +const createInternalSetupMock = (): jest.Mocked => ({ + configure: jest.fn(), +}); + +const createSetupMock = (): jest.Mocked => ({ + configure: jest.fn(), +}); + +type LoggingServiceContract = PublicMethodsOf; +const createMock = (): jest.Mocked => { + const service: jest.Mocked = { + setup: jest.fn(), + start: jest.fn(), stop: jest.fn(), }; - mocked.get.mockImplementation((...context) => ({ - ...mockLog, - context, - })); - mocked.asLoggerFactory.mockImplementation(() => mocked); - mocked.stop.mockResolvedValue(); - return mocked; -}; - -const collectLoggingServiceMock = (loggerFactory: LoggerFactory) => { - const mockLog = loggerFactory.get() as MockedLogger; - return { - debug: mockLog.debug.mock.calls, - error: mockLog.error.mock.calls, - fatal: mockLog.fatal.mock.calls, - info: mockLog.info.mock.calls, - log: mockLog.log.mock.calls, - trace: mockLog.trace.mock.calls, - warn: mockLog.warn.mock.calls, - }; -}; -const clearLoggingServiceMock = (loggerFactory: LoggerFactory) => { - const mockedLoggerFactory = (loggerFactory as unknown) as jest.Mocked; - mockedLoggerFactory.get.mockClear(); - mockedLoggerFactory.asLoggerFactory.mockClear(); - mockedLoggerFactory.upgrade.mockClear(); - mockedLoggerFactory.stop.mockClear(); + service.setup.mockReturnValue(createInternalSetupMock()); - const mockLog = loggerFactory.get() as MockedLogger; - mockLog.debug.mockClear(); - mockLog.info.mockClear(); - mockLog.warn.mockClear(); - mockLog.error.mockClear(); - mockLog.trace.mockClear(); - mockLog.fatal.mockClear(); - mockLog.log.mockClear(); + return service; }; export const loggingServiceMock = { - create: createLoggingServiceMock, - collect: collectLoggingServiceMock, - clear: clearLoggingServiceMock, - createLogger: loggerMock.create, + create: createMock, + createSetupContract: createSetupMock, + createInternalSetupContract: createInternalSetupMock, }; diff --git a/src/core/server/logging/logging_service.test.ts b/src/core/server/logging/logging_service.test.ts index 1e6c253c56c7b..5107db77304fc 100644 --- a/src/core/server/logging/logging_service.test.ts +++ b/src/core/server/logging/logging_service.test.ts @@ -16,167 +16,85 @@ * specific language governing permissions and limitations * under the License. */ - -const mockStreamWrite = jest.fn(); -jest.mock('fs', () => ({ - constants: {}, - createWriteStream: jest.fn(() => ({ write: mockStreamWrite })), -})); - -const dynamicProps = { pid: expect.any(Number) }; - -jest.mock('../../../legacy/server/logging/rotate', () => ({ - setupLoggingRotate: jest.fn().mockImplementation(() => Promise.resolve({})), -})); - -const timestamp = new Date(Date.UTC(2012, 1, 1, 14, 33, 22, 11)); -let mockConsoleLog: jest.SpyInstance; - -import { createWriteStream } from 'fs'; -const mockCreateWriteStream = (createWriteStream as unknown) as jest.Mock; - -import { LoggingService, config } from '.'; - -let service: LoggingService; -beforeEach(() => { - mockConsoleLog = jest.spyOn(global.console, 'log').mockReturnValue(undefined); - jest.spyOn(global, 'Date').mockImplementation(() => timestamp); - service = new LoggingService(); -}); - -afterEach(() => { - jest.restoreAllMocks(); - mockCreateWriteStream.mockClear(); - mockStreamWrite.mockClear(); -}); - -test('uses default memory buffer logger until config is provided', () => { - const bufferAppendSpy = jest.spyOn((service as any).bufferAppender, 'append'); - - const logger = service.get('test', 'context'); - logger.trace('trace message'); - - // We shouldn't create new buffer appender for another context. - const anotherLogger = service.get('test', 'context2'); - anotherLogger.fatal('fatal message', { some: 'value' }); - - expect(bufferAppendSpy).toHaveBeenCalledTimes(2); - expect(bufferAppendSpy.mock.calls[0][0]).toMatchSnapshot(dynamicProps); - expect(bufferAppendSpy.mock.calls[1][0]).toMatchSnapshot(dynamicProps); -}); - -test('flushes memory buffer logger and switches to real logger once config is provided', () => { - const logger = service.get('test', 'context'); - - logger.trace('buffered trace message'); - logger.info('buffered info message', { some: 'value' }); - logger.fatal('buffered fatal message'); - - const bufferAppendSpy = jest.spyOn((service as any).bufferAppender, 'append'); - - // Switch to console appender with `info` level, so that `trace` message won't go through. - service.upgrade( - config.schema.validate({ - appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, - root: { level: 'info' }, - }) - ); - - expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchSnapshot( - dynamicProps, - 'buffered messages' - ); - mockConsoleLog.mockClear(); - - // Now message should go straight to thew newly configured appender, not buffered one. - logger.info('some new info message'); - expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchSnapshot(dynamicProps, 'new messages'); - expect(bufferAppendSpy).not.toHaveBeenCalled(); -}); - -test('appends records via multiple appenders.', () => { - const loggerWithoutConfig = service.get('some-context'); - const testsLogger = service.get('tests'); - const testsChildLogger = service.get('tests', 'child'); - - loggerWithoutConfig.info('You know, just for your info.'); - testsLogger.warn('Config is not ready!'); - testsChildLogger.error('Too bad that config is not ready :/'); - testsChildLogger.info('Just some info that should not be logged.'); - - expect(mockConsoleLog).not.toHaveBeenCalled(); - expect(mockCreateWriteStream).not.toHaveBeenCalled(); - - service.upgrade( - config.schema.validate({ - appenders: { - default: { kind: 'console', layout: { kind: 'pattern' } }, - file: { kind: 'file', layout: { kind: 'pattern' }, path: 'path' }, - }, - loggers: [ - { appenders: ['file'], context: 'tests', level: 'warn' }, - { context: 'tests.child', level: 'error' }, - ], - }) - ); - - // Now all logs should added to configured appenders. - expect(mockConsoleLog).toHaveBeenCalledTimes(1); - expect(mockConsoleLog.mock.calls[0][0]).toMatchSnapshot('console logs'); - - expect(mockStreamWrite).toHaveBeenCalledTimes(2); - expect(mockStreamWrite.mock.calls[0][0]).toMatchSnapshot('file logs'); - expect(mockStreamWrite.mock.calls[1][0]).toMatchSnapshot('file logs'); -}); - -test('uses `root` logger if context is not specified.', () => { - service.upgrade( - config.schema.validate({ - appenders: { default: { kind: 'console', layout: { kind: 'pattern' } } }, - }) - ); - - const rootLogger = service.get(); - rootLogger.info('This message goes to a root context.'); - - expect(mockConsoleLog.mock.calls).toMatchSnapshot(); -}); - -test('`stop()` disposes all appenders.', async () => { - service.upgrade( - config.schema.validate({ - appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, - root: { level: 'info' }, - }) - ); - - const bufferDisposeSpy = jest.spyOn((service as any).bufferAppender, 'dispose'); - const consoleDisposeSpy = jest.spyOn((service as any).appenders.get('default'), 'dispose'); - - await service.stop(); - - expect(bufferDisposeSpy).toHaveBeenCalledTimes(1); - expect(consoleDisposeSpy).toHaveBeenCalledTimes(1); -}); - -test('asLoggerFactory() only allows to create new loggers.', () => { - const logger = service.asLoggerFactory().get('test', 'context'); - - service.upgrade( - config.schema.validate({ - appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, - root: { level: 'all' }, - }) - ); - - logger.trace('buffered trace message'); - logger.info('buffered info message', { some: 'value' }); - logger.fatal('buffered fatal message'); - - expect(Object.keys(service.asLoggerFactory())).toEqual(['get']); - - expect(mockConsoleLog).toHaveBeenCalledTimes(3); - expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchSnapshot(dynamicProps); - expect(JSON.parse(mockConsoleLog.mock.calls[1][0])).toMatchSnapshot(dynamicProps); - expect(JSON.parse(mockConsoleLog.mock.calls[2][0])).toMatchSnapshot(dynamicProps); +import { of, Subject } from 'rxjs'; + +import { LoggingService, InternalLoggingServiceSetup } from './logging_service'; +import { loggingSystemMock } from './logging_system.mock'; +import { LoggerContextConfigType } from './logging_config'; + +describe('LoggingService', () => { + let loggingSystem: ReturnType; + let service: LoggingService; + let setup: InternalLoggingServiceSetup; + + beforeEach(() => { + loggingSystem = loggingSystemMock.create(); + service = new LoggingService({ logger: loggingSystem.asLoggerFactory() } as any); + setup = service.setup({ loggingSystem }); + }); + afterEach(() => { + service.stop(); + }); + + describe('setup', () => { + it('forwards configuration changes to logging system', () => { + const config1: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ context: 'subcontext', appenders: ['console'], level: 'warn' }], + }; + const config2: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ context: 'subcontext', appenders: ['default'], level: 'all' }], + }; + + setup.configure(['test', 'context'], of(config1, config2)); + expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( + 1, + ['test', 'context'], + config1 + ); + expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( + 2, + ['test', 'context'], + config2 + ); + }); + + it('stops forwarding first observable when called a second time', () => { + const updates$ = new Subject(); + const config1: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ context: 'subcontext', appenders: ['console'], level: 'warn' }], + }; + const config2: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ context: 'subcontext', appenders: ['default'], level: 'all' }], + }; + + setup.configure(['test', 'context'], updates$); + setup.configure(['test', 'context'], of(config1)); + updates$.next(config2); + expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( + 1, + ['test', 'context'], + config1 + ); + expect(loggingSystem.setContextConfig).not.toHaveBeenCalledWith(['test', 'context'], config2); + }); + }); + + describe('stop', () => { + it('stops forwarding updates to logging system', () => { + const updates$ = new Subject(); + const config1: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ context: 'subcontext', appenders: ['console'], level: 'warn' }], + }; + + setup.configure(['test', 'context'], updates$); + service.stop(); + updates$.next(config1); + expect(loggingSystem.setContextConfig).not.toHaveBeenCalledWith(['test', 'context'], config1); + }); + }); }); diff --git a/src/core/server/logging/logging_service.ts b/src/core/server/logging/logging_service.ts index 2e6f895724122..09051f8f07702 100644 --- a/src/core/server/logging/logging_service.ts +++ b/src/core/server/logging/logging_service.ts @@ -16,112 +16,88 @@ * specific language governing permissions and limitations * under the License. */ -import { Appenders, DisposableAppender } from './appenders/appenders'; -import { BufferAppender } from './appenders/buffer/buffer_appender'; -import { LogLevel } from './log_level'; -import { BaseLogger, Logger } from './logger'; -import { LoggerAdapter } from './logger_adapter'; -import { LoggerFactory } from './logger_factory'; -import { LoggingConfigType, LoggerConfigType, LoggingConfig } from './logging_config'; -export type ILoggingService = PublicMethodsOf; +import { Observable, Subscription } from 'rxjs'; +import { CoreService } from '../../types'; +import { LoggingConfig, LoggerContextConfigInput } from './logging_config'; +import { ILoggingSystem } from './logging_system'; +import { Logger } from './logger'; +import { CoreContext } from '../core_context'; + /** - * Service that is responsible for maintaining loggers and logger appenders. - * @internal + * Provides APIs to plugins for customizing the plugin's logger. + * @public */ -export class LoggingService implements LoggerFactory { - private config?: LoggingConfig; - private readonly appenders: Map = new Map(); - private readonly bufferAppender = new BufferAppender(); - private readonly loggers: Map = new Map(); - - public get(...contextParts: string[]): Logger { - const context = LoggingConfig.getLoggerContext(contextParts); - if (!this.loggers.has(context)) { - this.loggers.set(context, new LoggerAdapter(this.createLogger(context, this.config))); - } - return this.loggers.get(context)!; - } - - /** - * Safe wrapper that allows passing logging service as immutable LoggerFactory. - */ - public asLoggerFactory(): LoggerFactory { - return { get: (...contextParts: string[]) => this.get(...contextParts) }; - } - +export interface LoggingServiceSetup { /** - * Updates all current active loggers with the new config values. - * @param rawConfig New config instance. + * Customizes the logging config for the plugin's context. + * + * @remarks + * Assumes that that the `context` property of the individual `logger` items emitted by `config$` + * are relative to the plugin's logging context (defaults to `plugins.`). + * + * @example + * Customize the configuration for the plugins.data.search context. + * ```ts + * core.logging.configure( + * of({ + * appenders: new Map(), + * loggers: [{ context: 'search', appenders: ['default'] }] + * }) + * ) + * ``` + * + * @param config$ */ - public upgrade(rawConfig: LoggingConfigType) { - const config = new LoggingConfig(rawConfig); - // Config update is asynchronous and may require some time to complete, so we should invalidate - // config so that new loggers will be using BufferAppender until newly configured appenders are ready. - this.config = undefined; - - // Appenders must be reset, so we first dispose of the current ones, then - // build up a new set of appenders. - for (const appender of this.appenders.values()) { - appender.dispose(); - } - this.appenders.clear(); + configure(config$: Observable): void; +} - for (const [appenderKey, appenderConfig] of config.appenders) { - this.appenders.set(appenderKey, Appenders.create(appenderConfig)); - } +/** @internal */ +export interface InternalLoggingServiceSetup { + configure(contextParts: string[], config$: Observable): void; +} - for (const [loggerKey, loggerAdapter] of this.loggers) { - loggerAdapter.updateLogger(this.createLogger(loggerKey, config)); - } +interface SetupDeps { + loggingSystem: ILoggingSystem; +} - this.config = config; +/** @internal */ +export class LoggingService implements CoreService { + private readonly subscriptions = new Map(); + private readonly log: Logger; - // Re-log all buffered log records with newly configured appenders. - for (const logRecord of this.bufferAppender.flush()) { - this.get(logRecord.context).log(logRecord); - } + constructor(coreContext: CoreContext) { + this.log = coreContext.logger.get('logging'); } - /** - * Disposes all loggers (closes log files, clears buffers etc.). Service is not usable after - * calling of this method until new config is provided via `upgrade` method. - * @returns Promise that is resolved once all loggers are successfully disposed. - */ - public async stop() { - for (const appender of this.appenders.values()) { - await appender.dispose(); - } - - await this.bufferAppender.dispose(); - - this.appenders.clear(); - this.loggers.clear(); + public setup({ loggingSystem }: SetupDeps) { + return { + configure: (contextParts: string[], config$: Observable) => { + const contextName = LoggingConfig.getLoggerContext(contextParts); + this.log.debug(`Setting custom config for context [${contextName}]`); + + const existingSubscription = this.subscriptions.get(contextName); + if (existingSubscription) { + existingSubscription.unsubscribe(); + } + + // Might be fancier way to do this with rxjs, but this works and is simple to understand + this.subscriptions.set( + contextName, + config$.subscribe((config) => { + this.log.debug(`Updating logging config for context [${contextName}]`); + loggingSystem.setContextConfig(contextParts, config); + }) + ); + }, + }; } - private createLogger(context: string, config: LoggingConfig | undefined) { - if (config === undefined) { - // If we don't have config yet, use `buffered` appender that will store all logged messages in the memory - // until the config is ready. - return new BaseLogger(context, LogLevel.All, [this.bufferAppender], this.asLoggerFactory()); - } - - const { level, appenders } = this.getLoggerConfigByContext(config, context); - const loggerLevel = LogLevel.fromId(level); - const loggerAppenders = appenders.map((appenderKey) => this.appenders.get(appenderKey)!); + public start() {} - return new BaseLogger(context, loggerLevel, loggerAppenders, this.asLoggerFactory()); - } - - private getLoggerConfigByContext(config: LoggingConfig, context: string): LoggerConfigType { - const loggerConfig = config.loggers.get(context); - if (loggerConfig !== undefined) { - return loggerConfig; + public stop() { + for (const [, subscription] of this.subscriptions) { + subscription.unsubscribe(); } - - // If we don't have configuration for the specified context and it's the "nested" one (eg. `foo.bar.baz`), - // let's move up to the parent context (eg. `foo.bar`) and check if it has config we can rely on. Otherwise - // we fallback to the `root` context that should always be defined (enforced by configuration schema). - return this.getLoggerConfigByContext(config, LoggingConfig.getParentLoggerContext(context)); } } diff --git a/src/core/server/logging/logging_system.mock.ts b/src/core/server/logging/logging_system.mock.ts new file mode 100644 index 0000000000000..ac1e9b5196002 --- /dev/null +++ b/src/core/server/logging/logging_system.mock.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Test helpers to simplify mocking logs and collecting all their outputs +import { ILoggingSystem } from './logging_system'; +import { LoggerFactory } from './logger_factory'; +import { loggerMock, MockedLogger } from './logger.mock'; + +const createLoggingSystemMock = () => { + const mockLog = loggerMock.create(); + + mockLog.get.mockImplementation((...context) => ({ + ...mockLog, + context, + })); + + const mocked: jest.Mocked = { + get: jest.fn(), + asLoggerFactory: jest.fn(), + setContextConfig: jest.fn(), + upgrade: jest.fn(), + stop: jest.fn(), + }; + mocked.get.mockImplementation((...context) => ({ + ...mockLog, + context, + })); + mocked.asLoggerFactory.mockImplementation(() => mocked); + mocked.stop.mockResolvedValue(); + return mocked; +}; + +const collectLoggingSystemMock = (loggerFactory: LoggerFactory) => { + const mockLog = loggerFactory.get() as MockedLogger; + return { + debug: mockLog.debug.mock.calls, + error: mockLog.error.mock.calls, + fatal: mockLog.fatal.mock.calls, + info: mockLog.info.mock.calls, + log: mockLog.log.mock.calls, + trace: mockLog.trace.mock.calls, + warn: mockLog.warn.mock.calls, + }; +}; + +const clearLoggingSystemMock = (loggerFactory: LoggerFactory) => { + const mockedLoggerFactory = (loggerFactory as unknown) as jest.Mocked; + mockedLoggerFactory.get.mockClear(); + mockedLoggerFactory.asLoggerFactory.mockClear(); + mockedLoggerFactory.upgrade.mockClear(); + mockedLoggerFactory.stop.mockClear(); + + const mockLog = loggerFactory.get() as MockedLogger; + mockLog.debug.mockClear(); + mockLog.info.mockClear(); + mockLog.warn.mockClear(); + mockLog.error.mockClear(); + mockLog.trace.mockClear(); + mockLog.fatal.mockClear(); + mockLog.log.mockClear(); +}; + +export const loggingSystemMock = { + create: createLoggingSystemMock, + collect: collectLoggingSystemMock, + clear: clearLoggingSystemMock, + createLogger: loggerMock.create, +}; diff --git a/src/core/server/logging/logging_system.test.ts b/src/core/server/logging/logging_system.test.ts new file mode 100644 index 0000000000000..f73e40fe320dc --- /dev/null +++ b/src/core/server/logging/logging_system.test.ts @@ -0,0 +1,348 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const mockStreamWrite = jest.fn(); +jest.mock('fs', () => ({ + constants: {}, + createWriteStream: jest.fn(() => ({ write: mockStreamWrite })), +})); + +const dynamicProps = { pid: expect.any(Number) }; + +jest.mock('../../../legacy/server/logging/rotate', () => ({ + setupLoggingRotate: jest.fn().mockImplementation(() => Promise.resolve({})), +})); + +const timestamp = new Date(Date.UTC(2012, 1, 1, 14, 33, 22, 11)); +let mockConsoleLog: jest.SpyInstance; + +import { createWriteStream } from 'fs'; +const mockCreateWriteStream = (createWriteStream as unknown) as jest.Mock; + +import { LoggingSystem, config } from '.'; + +let system: LoggingSystem; +beforeEach(() => { + mockConsoleLog = jest.spyOn(global.console, 'log').mockReturnValue(undefined); + jest.spyOn(global, 'Date').mockImplementation(() => timestamp); + system = new LoggingSystem(); +}); + +afterEach(() => { + jest.restoreAllMocks(); + mockCreateWriteStream.mockClear(); + mockStreamWrite.mockClear(); +}); + +test('uses default memory buffer logger until config is provided', () => { + const bufferAppendSpy = jest.spyOn((system as any).bufferAppender, 'append'); + + const logger = system.get('test', 'context'); + logger.trace('trace message'); + + // We shouldn't create new buffer appender for another context. + const anotherLogger = system.get('test', 'context2'); + anotherLogger.fatal('fatal message', { some: 'value' }); + + expect(bufferAppendSpy).toHaveBeenCalledTimes(2); + expect(bufferAppendSpy.mock.calls[0][0]).toMatchSnapshot(dynamicProps); + expect(bufferAppendSpy.mock.calls[1][0]).toMatchSnapshot(dynamicProps); +}); + +test('flushes memory buffer logger and switches to real logger once config is provided', () => { + const logger = system.get('test', 'context'); + + logger.trace('buffered trace message'); + logger.info('buffered info message', { some: 'value' }); + logger.fatal('buffered fatal message'); + + const bufferAppendSpy = jest.spyOn((system as any).bufferAppender, 'append'); + + // Switch to console appender with `info` level, so that `trace` message won't go through. + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, + root: { level: 'info' }, + }) + ); + + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchSnapshot( + dynamicProps, + 'buffered messages' + ); + mockConsoleLog.mockClear(); + + // Now message should go straight to thew newly configured appender, not buffered one. + logger.info('some new info message'); + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchSnapshot(dynamicProps, 'new messages'); + expect(bufferAppendSpy).not.toHaveBeenCalled(); +}); + +test('appends records via multiple appenders.', () => { + const loggerWithoutConfig = system.get('some-context'); + const testsLogger = system.get('tests'); + const testsChildLogger = system.get('tests', 'child'); + + loggerWithoutConfig.info('You know, just for your info.'); + testsLogger.warn('Config is not ready!'); + testsChildLogger.error('Too bad that config is not ready :/'); + testsChildLogger.info('Just some info that should not be logged.'); + + expect(mockConsoleLog).not.toHaveBeenCalled(); + expect(mockCreateWriteStream).not.toHaveBeenCalled(); + + system.upgrade( + config.schema.validate({ + appenders: { + default: { kind: 'console', layout: { kind: 'pattern' } }, + file: { kind: 'file', layout: { kind: 'pattern' }, path: 'path' }, + }, + loggers: [ + { appenders: ['file'], context: 'tests', level: 'warn' }, + { context: 'tests.child', level: 'error' }, + ], + }) + ); + + // Now all logs should added to configured appenders. + expect(mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mockConsoleLog.mock.calls[0][0]).toMatchSnapshot('console logs'); + + expect(mockStreamWrite).toHaveBeenCalledTimes(2); + expect(mockStreamWrite.mock.calls[0][0]).toMatchSnapshot('file logs'); + expect(mockStreamWrite.mock.calls[1][0]).toMatchSnapshot('file logs'); +}); + +test('uses `root` logger if context is not specified.', () => { + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'pattern' } } }, + }) + ); + + const rootLogger = system.get(); + rootLogger.info('This message goes to a root context.'); + + expect(mockConsoleLog.mock.calls).toMatchSnapshot(); +}); + +test('`stop()` disposes all appenders.', async () => { + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, + root: { level: 'info' }, + }) + ); + + const bufferDisposeSpy = jest.spyOn((system as any).bufferAppender, 'dispose'); + const consoleDisposeSpy = jest.spyOn((system as any).appenders.get('default'), 'dispose'); + + await system.stop(); + + expect(bufferDisposeSpy).toHaveBeenCalledTimes(1); + expect(consoleDisposeSpy).toHaveBeenCalledTimes(1); +}); + +test('asLoggerFactory() only allows to create new loggers.', () => { + const logger = system.asLoggerFactory().get('test', 'context'); + + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, + root: { level: 'all' }, + }) + ); + + logger.trace('buffered trace message'); + logger.info('buffered info message', { some: 'value' }); + logger.fatal('buffered fatal message'); + + expect(Object.keys(system.asLoggerFactory())).toEqual(['get']); + + expect(mockConsoleLog).toHaveBeenCalledTimes(3); + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchSnapshot(dynamicProps); + expect(JSON.parse(mockConsoleLog.mock.calls[1][0])).toMatchSnapshot(dynamicProps); + expect(JSON.parse(mockConsoleLog.mock.calls[2][0])).toMatchSnapshot(dynamicProps); +}); + +test('setContextConfig() updates config with relative contexts', () => { + const testsLogger = system.get('tests'); + const testsChildLogger = system.get('tests', 'child'); + const testsGrandchildLogger = system.get('tests', 'child', 'grandchild'); + + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, + root: { level: 'info' }, + }) + ); + + system.setContextConfig(['tests', 'child'], { + appenders: new Map([ + [ + 'custom', + { kind: 'console', layout: { kind: 'pattern', pattern: '[%level][%logger] %message' } }, + ], + ]), + loggers: [{ context: 'grandchild', appenders: ['default', 'custom'], level: 'debug' }], + }); + + testsLogger.warn('tests log to default!'); + testsChildLogger.error('tests.child log to default!'); + testsGrandchildLogger.debug('tests.child.grandchild log to default and custom!'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(4); + // Parent contexts are unaffected + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchObject({ + context: 'tests', + message: 'tests log to default!', + level: 'WARN', + }); + expect(JSON.parse(mockConsoleLog.mock.calls[1][0])).toMatchObject({ + context: 'tests.child', + message: 'tests.child log to default!', + level: 'ERROR', + }); + // Customized context is logged in both appender formats + expect(JSON.parse(mockConsoleLog.mock.calls[2][0])).toMatchObject({ + context: 'tests.child.grandchild', + message: 'tests.child.grandchild log to default and custom!', + level: 'DEBUG', + }); + expect(mockConsoleLog.mock.calls[3][0]).toMatchInlineSnapshot( + `"[DEBUG][tests.child.grandchild] tests.child.grandchild log to default and custom!"` + ); +}); + +test('custom context configs are applied on subsequent calls to update()', () => { + system.setContextConfig(['tests', 'child'], { + appenders: new Map([ + [ + 'custom', + { kind: 'console', layout: { kind: 'pattern', pattern: '[%level][%logger] %message' } }, + ], + ]), + loggers: [{ context: 'grandchild', appenders: ['default', 'custom'], level: 'debug' }], + }); + + // Calling upgrade after setContextConfig should not throw away the context-specific config + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, + root: { level: 'info' }, + }) + ); + + system + .get('tests', 'child', 'grandchild') + .debug('tests.child.grandchild log to default and custom!'); + + // Customized context is logged in both appender formats still + expect(mockConsoleLog).toHaveBeenCalledTimes(2); + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchObject({ + context: 'tests.child.grandchild', + message: 'tests.child.grandchild log to default and custom!', + level: 'DEBUG', + }); + expect(mockConsoleLog.mock.calls[1][0]).toMatchInlineSnapshot( + `"[DEBUG][tests.child.grandchild] tests.child.grandchild log to default and custom!"` + ); +}); + +test('subsequent calls to setContextConfig() for the same context override the previous config', () => { + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, + root: { level: 'info' }, + }) + ); + + system.setContextConfig(['tests', 'child'], { + appenders: new Map([ + [ + 'custom', + { kind: 'console', layout: { kind: 'pattern', pattern: '[%level][%logger] %message' } }, + ], + ]), + loggers: [{ context: 'grandchild', appenders: ['default', 'custom'], level: 'debug' }], + }); + + // Call again, this time with level: 'warn' and a different pattern + system.setContextConfig(['tests', 'child'], { + appenders: new Map([ + [ + 'custom', + { + kind: 'console', + layout: { kind: 'pattern', pattern: '[%level][%logger] second pattern! %message' }, + }, + ], + ]), + loggers: [{ context: 'grandchild', appenders: ['default', 'custom'], level: 'warn' }], + }); + + const logger = system.get('tests', 'child', 'grandchild'); + logger.debug('this should not show anywhere!'); + logger.warn('tests.child.grandchild log to default and custom!'); + + // Only the warn log should have been logged + expect(mockConsoleLog).toHaveBeenCalledTimes(2); + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchObject({ + context: 'tests.child.grandchild', + message: 'tests.child.grandchild log to default and custom!', + level: 'WARN', + }); + expect(mockConsoleLog.mock.calls[1][0]).toMatchInlineSnapshot( + `"[WARN ][tests.child.grandchild] second pattern! tests.child.grandchild log to default and custom!"` + ); +}); + +test('subsequent calls to setContextConfig() for the same context can disable the previous config', () => { + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, + root: { level: 'info' }, + }) + ); + + system.setContextConfig(['tests', 'child'], { + appenders: new Map([ + [ + 'custom', + { kind: 'console', layout: { kind: 'pattern', pattern: '[%level][%logger] %message' } }, + ], + ]), + loggers: [{ context: 'grandchild', appenders: ['default', 'custom'], level: 'debug' }], + }); + + // Call again, this time no customizations (effectively disabling) + system.setContextConfig(['tests', 'child'], {}); + + const logger = system.get('tests', 'child', 'grandchild'); + logger.debug('this should not show anywhere!'); + logger.warn('tests.child.grandchild log to default!'); + + // Only the warn log should have been logged once on the default appender + expect(mockConsoleLog).toHaveBeenCalledTimes(1); + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchObject({ + context: 'tests.child.grandchild', + message: 'tests.child.grandchild log to default!', + level: 'WARN', + }); +}); diff --git a/src/core/server/logging/logging_system.ts b/src/core/server/logging/logging_system.ts new file mode 100644 index 0000000000000..0bab9534d2d05 --- /dev/null +++ b/src/core/server/logging/logging_system.ts @@ -0,0 +1,185 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Appenders, DisposableAppender } from './appenders/appenders'; +import { BufferAppender } from './appenders/buffer/buffer_appender'; +import { LogLevel } from './log_level'; +import { BaseLogger, Logger } from './logger'; +import { LoggerAdapter } from './logger_adapter'; +import { LoggerFactory } from './logger_factory'; +import { + LoggingConfigType, + LoggerConfigType, + LoggingConfig, + LoggerContextConfigType, + LoggerContextConfigInput, + loggerContextConfigSchema, +} from './logging_config'; + +export type ILoggingSystem = PublicMethodsOf; + +/** + * System that is responsible for maintaining loggers and logger appenders. + * @internal + */ +export class LoggingSystem implements LoggerFactory { + /** The configuration set by the user. */ + private baseConfig?: LoggingConfig; + /** The fully computed configuration extended by context-specific configurations set programmatically */ + private computedConfig?: LoggingConfig; + private readonly appenders: Map = new Map(); + private readonly bufferAppender = new BufferAppender(); + private readonly loggers: Map = new Map(); + private readonly contextConfigs = new Map(); + + public get(...contextParts: string[]): Logger { + const context = LoggingConfig.getLoggerContext(contextParts); + if (!this.loggers.has(context)) { + this.loggers.set(context, new LoggerAdapter(this.createLogger(context, this.computedConfig))); + } + return this.loggers.get(context)!; + } + + /** + * Safe wrapper that allows passing logging service as immutable LoggerFactory. + */ + public asLoggerFactory(): LoggerFactory { + return { get: (...contextParts: string[]) => this.get(...contextParts) }; + } + + /** + * Updates all current active loggers with the new config values. + * @param rawConfig New config instance. + */ + public upgrade(rawConfig: LoggingConfigType) { + const config = new LoggingConfig(rawConfig)!; + this.applyBaseConfig(config); + } + + /** + * Customizes the logging config for a specific context. + * + * @remarks + * Assumes that that the `context` property of the individual items in `rawConfig.loggers` + * are relative to the `baseContextParts`. + * + * @example + * Customize the configuration for the plugins.data.search context. + * ```ts + * loggingSystem.setContextConfig( + * ['plugins', 'data'], + * { + * loggers: [{ context: 'search', appenders: ['default'] }] + * } + * ) + * ``` + * + * @param baseContextParts + * @param rawConfig + */ + public setContextConfig(baseContextParts: string[], rawConfig: LoggerContextConfigInput) { + const context = LoggingConfig.getLoggerContext(baseContextParts); + const contextConfig = loggerContextConfigSchema.validate(rawConfig); + this.contextConfigs.set(context, { + ...contextConfig, + // Automatically prepend the base context to the logger sub-contexts + loggers: contextConfig.loggers.map((l) => ({ + ...l, + context: LoggingConfig.getLoggerContext([context, l.context]), + })), + }); + + // If we already have a base config, apply the config. If not, custom context configs + // will be picked up on next call to `upgrade`. + if (this.baseConfig) { + this.applyBaseConfig(this.baseConfig); + } + } + + /** + * Disposes all loggers (closes log files, clears buffers etc.). Service is not usable after + * calling of this method until new config is provided via `upgrade` method. + * @returns Promise that is resolved once all loggers are successfully disposed. + */ + public async stop() { + await Promise.all([...this.appenders.values()].map((a) => a.dispose())); + + await this.bufferAppender.dispose(); + + this.appenders.clear(); + this.loggers.clear(); + } + + private createLogger(context: string, config: LoggingConfig | undefined) { + if (config === undefined) { + // If we don't have config yet, use `buffered` appender that will store all logged messages in the memory + // until the config is ready. + return new BaseLogger(context, LogLevel.All, [this.bufferAppender], this.asLoggerFactory()); + } + + const { level, appenders } = this.getLoggerConfigByContext(config, context); + const loggerLevel = LogLevel.fromId(level); + const loggerAppenders = appenders.map((appenderKey) => this.appenders.get(appenderKey)!); + + return new BaseLogger(context, loggerLevel, loggerAppenders, this.asLoggerFactory()); + } + + private getLoggerConfigByContext(config: LoggingConfig, context: string): LoggerConfigType { + const loggerConfig = config.loggers.get(context); + if (loggerConfig !== undefined) { + return loggerConfig; + } + + // If we don't have configuration for the specified context and it's the "nested" one (eg. `foo.bar.baz`), + // let's move up to the parent context (eg. `foo.bar`) and check if it has config we can rely on. Otherwise + // we fallback to the `root` context that should always be defined (enforced by configuration schema). + return this.getLoggerConfigByContext(config, LoggingConfig.getParentLoggerContext(context)); + } + + private applyBaseConfig(newBaseConfig: LoggingConfig) { + const computedConfig = [...this.contextConfigs.values()].reduce( + (baseConfig, contextConfig) => baseConfig.extend(contextConfig), + newBaseConfig + ); + + // Appenders must be reset, so we first dispose of the current ones, then + // build up a new set of appenders. + for (const appender of this.appenders.values()) { + appender.dispose(); + } + this.appenders.clear(); + + for (const [appenderKey, appenderConfig] of computedConfig.appenders) { + this.appenders.set(appenderKey, Appenders.create(appenderConfig)); + } + + for (const [loggerKey, loggerAdapter] of this.loggers) { + loggerAdapter.updateLogger(this.createLogger(loggerKey, computedConfig)); + } + + // We keep a reference to the base config so we can properly extend it + // on each config change. + this.baseConfig = newBaseConfig; + this.computedConfig = computedConfig; + + // Re-log all buffered log records with newly configured appenders. + for (const logRecord of this.bufferAppender.flush()) { + this.get(logRecord.context).log(logRecord); + } + } +} diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index f3ae5462f1631..0770e8843e2f6 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -19,6 +19,7 @@ import { of } from 'rxjs'; import { duration } from 'moment'; import { PluginInitializerContext, CoreSetup, CoreStart, StartServicesAccessor } from '.'; +import { loggingSystemMock } from './logging/logging_system.mock'; import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -42,7 +43,7 @@ export { sessionStorageMock } from './http/cookie_session_storage.mocks'; export { configServiceMock } from './config/config_service.mock'; export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; export { httpServiceMock } from './http/http_service.mock'; -export { loggingServiceMock } from './logging/logging_service.mock'; +export { loggingSystemMock } from './logging/logging_system.mock'; export { savedObjectsRepositoryMock } from './saved_objects/service/lib/repository.mock'; export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; export { typeRegistryMock as savedObjectsTypeRegistryMock } from './saved_objects/saved_objects_type_registry.mock'; @@ -78,7 +79,7 @@ export function pluginInitializerContextConfigMock(config: T) { function pluginInitializerContextMock(config: T = {} as T) { const mock: PluginInitializerContext = { opaqueId: Symbol(), - logger: loggingServiceMock.create(), + logger: loggingSystemMock.create(), env: { mode: { dev: true, @@ -130,6 +131,7 @@ function createCoreSetupMock({ metrics: metricsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, uuid: uuidServiceMock.createSetupContract(), + logging: loggingServiceMock.createSetupContract(), getStartServices: jest .fn, object, any]>, []>() .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), @@ -163,6 +165,7 @@ function createInternalCoreSetupMock() { httpResources: httpResourcesMock.createSetupContract(), rendering: renderingMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), + logging: loggingServiceMock.createInternalSetupContract(), }; return setupDeps; } diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts index 979accb1f769e..5ffdef88104c8 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts @@ -20,12 +20,12 @@ import { PluginDiscoveryErrorType } from './plugin_discovery_error'; import { mockReadFile } from './plugin_manifest_parser.test.mocks'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { resolve } from 'path'; import { parseManifest } from './plugin_manifest_parser'; -const logger = loggingServiceMock.createLogger(); +const logger = loggingSystemMock.createLogger(); const pluginPath = resolve('path', 'existent-dir'); const pluginManifestPath = resolve(pluginPath, 'kibana.json'); const packageInfo = { @@ -105,9 +105,9 @@ test('logs warning if pluginId is not in camelCase format', async () => { cb(null, Buffer.from(JSON.stringify({ id: 'some_name', version: 'kibana', server: true }))); }); - expect(loggingServiceMock.collect(logger).warn).toHaveLength(0); + expect(loggingSystemMock.collect(logger).warn).toHaveLength(0); await parseManifest(pluginPath, packageInfo, logger); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Expect plugin \\"id\\" in camelCase, but found: some_name", diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.ts b/src/core/server/plugins/discovery/plugins_discovery.test.ts index 73f274957cbc4..1c42f5dcfc7a7 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.ts @@ -19,7 +19,7 @@ import { mockPackage, mockReaddir, mockReadFile, mockStat } from './plugins_discovery.test.mocks'; import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { resolve } from 'path'; import { first, map, toArray } from 'rxjs/operators'; @@ -37,7 +37,7 @@ const TEST_PLUGIN_SEARCH_PATHS = { }; const TEST_EXTRA_PLUGIN_PATH = resolve(process.cwd(), 'my-extra-plugin'); -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); beforeEach(() => { mockReaddir.mockImplementation((path, cb) => { @@ -221,7 +221,7 @@ test('logs a warning about --plugin-path when used in development', async () => logger, }); - expect(loggingServiceMock.collect(logger).warn).toEqual([ + expect(loggingSystemMock.collect(logger).warn).toEqual([ [ `Explicit plugin paths [${TEST_EXTRA_PLUGIN_PATH}] should only be used in development. Relative imports may not work properly in production.`, ], @@ -263,5 +263,5 @@ test('does not log a warning about --plugin-path when used in production', async logger, }); - expect(loggingServiceMock.collect(logger).warn).toEqual([]); + expect(loggingSystemMock.collect(logger).warn).toEqual([]); }); diff --git a/src/core/server/plugins/integration_tests/plugins_service.test.ts b/src/core/server/plugins/integration_tests/plugins_service.test.ts index 04f570cca489b..e676c789449ca 100644 --- a/src/core/server/plugins/integration_tests/plugins_service.test.ts +++ b/src/core/server/plugins/integration_tests/plugins_service.test.ts @@ -27,13 +27,13 @@ import { getEnvOptions } from '../../config/__mocks__/env'; import { BehaviorSubject, from } from 'rxjs'; import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; import { config } from '../plugins_config'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { coreMock } from '../../mocks'; import { Plugin } from '../types'; import { PluginWrapper } from '../plugin'; describe('PluginsService', () => { - const logger = loggingServiceMock.create(); + const logger = loggingSystemMock.create(); let pluginsService: PluginsService; const createPlugin = ( diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 8d82d96f949c7..ec0a3986b4877 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -26,14 +26,14 @@ import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { coreMock } from '../mocks'; import { configServiceMock } from '../config/config_service.mock'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { PluginWrapper } from './plugin'; import { PluginManifest } from './types'; import { createPluginInitializerContext, createPluginSetupContext } from './plugin_context'; const mockPluginInitializer = jest.fn(); -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); jest.doMock( join('plugin-with-initializer-path', 'server'), () => ({ plugin: mockPluginInitializer }), diff --git a/src/core/server/plugins/plugin.ts b/src/core/server/plugins/plugin.ts index d7cfaa14d2343..2e5881c651843 100644 --- a/src/core/server/plugins/plugin.ts +++ b/src/core/server/plugins/plugin.ts @@ -95,8 +95,6 @@ export class PluginWrapper< public async setup(setupContext: CoreSetup, plugins: TPluginsSetup) { this.instance = this.createPluginInstance(); - this.log.debug('Setting up plugin'); - return this.instance.setup(setupContext, plugins); } @@ -112,8 +110,6 @@ export class PluginWrapper< throw new Error(`Plugin "${this.name}" can't be started since it isn't set up.`); } - this.log.debug('Starting plugin'); - const startContract = await this.instance.start(startContext, plugins); this.startDependencies$.next([startContext, plugins, startContract]); return startContract; @@ -127,8 +123,6 @@ export class PluginWrapper< throw new Error(`Plugin "${this.name}" can't be stopped since it isn't set up.`); } - this.log.info('Stopping plugin'); - if (typeof this.instance.stop === 'function') { await this.instance.stop(); } diff --git a/src/core/server/plugins/plugin_context.test.ts b/src/core/server/plugins/plugin_context.test.ts index 54350d96984b4..69b354661abc9 100644 --- a/src/core/server/plugins/plugin_context.test.ts +++ b/src/core/server/plugins/plugin_context.test.ts @@ -22,14 +22,14 @@ import { first } from 'rxjs/operators'; import { createPluginInitializerContext } from './plugin_context'; import { CoreContext } from '../core_context'; import { Env } from '../config'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { rawConfigServiceMock } from '../config/raw_config_service.mock'; import { getEnvOptions } from '../config/__mocks__/env'; import { PluginManifest } from './types'; import { Server } from '../server'; import { fromRoot } from '../utils'; -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); let coreId: symbol; let env: Env; diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 31e36db49223a..32bc8dc088cad 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -166,6 +166,9 @@ export function createPluginSetupContext( csp: deps.http.csp, getServerInfo: deps.http.getServerInfo, }, + logging: { + configure: (config$) => deps.logging.configure(['plugins', plugin.name], config$), + }, metrics: { getOpsMetrics$: deps.metrics.getOpsMetrics$, }, diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 6f8d15838641f..c277dc85e5e04 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -28,7 +28,7 @@ import { ConfigPath, ConfigService, Env } from '../config'; import { rawConfigServiceMock } from '../config/raw_config_service.mock'; import { getEnvOptions } from '../config/__mocks__/env'; import { coreMock } from '../mocks'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { PluginDiscoveryError } from './discovery'; import { PluginWrapper } from './plugin'; import { PluginsService } from './plugins_service'; @@ -47,7 +47,7 @@ let env: Env; let mockPluginSystem: jest.Mocked; const setupDeps = coreMock.createInternalSetup(); -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); expect.addSnapshotSerializer(createAbsolutePathSerializer()); @@ -138,7 +138,7 @@ describe('PluginsService', () => { [Error: Failed to initialize plugins: Invalid JSON (invalid-manifest, path-1)] `); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Invalid JSON (invalid-manifest, path-1)], @@ -159,7 +159,7 @@ describe('PluginsService', () => { [Error: Failed to initialize plugins: Incompatible version (incompatible-version, path-3)] `); - expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ [Error: Incompatible version (incompatible-version, path-3)], @@ -238,7 +238,7 @@ describe('PluginsService', () => { expect(mockPluginSystem.setupPlugins).toHaveBeenCalledTimes(1); expect(mockPluginSystem.setupPlugins).toHaveBeenCalledWith(setupDeps); - expect(loggingServiceMock.collect(logger).info).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` Array [ Array [ "Plugin \\"explicitly-disabled-plugin\\" is disabled.", @@ -360,7 +360,7 @@ describe('PluginsService', () => { { coreId, env, logger, configService } ); - const logs = loggingServiceMock.collect(logger); + const logs = loggingSystemMock.collect(logger); expect(logs.info).toHaveLength(0); expect(logs.error).toHaveLength(0); }); diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 70983e4fd087b..a40df70228ff3 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -28,7 +28,7 @@ import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { configServiceMock } from '../config/config_service.mock'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { PluginWrapper } from './plugin'; import { PluginName } from './types'; @@ -36,7 +36,7 @@ import { PluginsSystem } from './plugins_system'; import { coreMock } from '../mocks'; import { Logger } from '../logging'; -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); function createPlugin( id: string, { diff --git a/src/core/server/root/index.test.mocks.ts b/src/core/server/root/index.test.mocks.ts index 1d3add66d7c22..ef4a40fa3db2d 100644 --- a/src/core/server/root/index.test.mocks.ts +++ b/src/core/server/root/index.test.mocks.ts @@ -17,10 +17,10 @@ * under the License. */ -import { loggingServiceMock } from '../logging/logging_service.mock'; -export const logger = loggingServiceMock.create(); -jest.doMock('../logging/logging_service', () => ({ - LoggingService: jest.fn(() => logger), +import { loggingSystemMock } from '../logging/logging_system.mock'; +export const logger = loggingSystemMock.create(); +jest.doMock('../logging/logging_system', () => ({ + LoggingSystem: jest.fn(() => logger), })); import { configServiceMock } from '../config/config_service.mock'; diff --git a/src/core/server/root/index.ts b/src/core/server/root/index.ts index d6d0c641e00b0..5e9722de03dee 100644 --- a/src/core/server/root/index.ts +++ b/src/core/server/root/index.ts @@ -21,7 +21,7 @@ import { ConnectableObservable, Subscription } from 'rxjs'; import { first, map, publishReplay, switchMap, tap } from 'rxjs/operators'; import { Env, RawConfigurationProvider } from '../config'; -import { Logger, LoggerFactory, LoggingConfigType, LoggingService } from '../logging'; +import { Logger, LoggerFactory, LoggingConfigType, LoggingSystem } from '../logging'; import { Server } from '../server'; /** @@ -30,7 +30,7 @@ import { Server } from '../server'; export class Root { public readonly logger: LoggerFactory; private readonly log: Logger; - private readonly loggingService: LoggingService; + private readonly loggingSystem: LoggingSystem; private readonly server: Server; private loggingConfigSubscription?: Subscription; @@ -39,10 +39,10 @@ export class Root { env: Env, private readonly onShutdown?: (reason?: Error | string) => void ) { - this.loggingService = new LoggingService(); - this.logger = this.loggingService.asLoggerFactory(); + this.loggingSystem = new LoggingSystem(); + this.logger = this.loggingSystem.asLoggerFactory(); this.log = this.logger.get('root'); - this.server = new Server(rawConfigProvider, env, this.logger); + this.server = new Server(rawConfigProvider, env, this.loggingSystem); } public async setup() { @@ -86,7 +86,7 @@ export class Root { this.loggingConfigSubscription.unsubscribe(); this.loggingConfigSubscription = undefined; } - await this.loggingService.stop(); + await this.loggingSystem.stop(); if (this.onShutdown !== undefined) { this.onShutdown(reason); @@ -99,7 +99,7 @@ export class Root { const update$ = configService.getConfig$().pipe( // always read the logging config when the underlying config object is re-read switchMap(() => configService.atPath('logging')), - map((config) => this.loggingService.upgrade(config)), + map((config) => this.loggingSystem.upgrade(config)), // This specifically console.logs because we were not able to configure the logger. // eslint-disable-next-line no-console tap({ error: (err) => console.error('Configuring logger failed:', err) }), diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index a364710322524..6287d47f99f62 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -20,11 +20,11 @@ import _ from 'lodash'; import { SavedObjectUnsanitizedDoc } from '../../serialization'; import { DocumentMigrator } from './document_migrator'; -import { loggingServiceMock } from '../../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { SavedObjectsType } from '../../types'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; -const mockLoggerFactory = loggingServiceMock.create(); +const mockLoggerFactory = loggingSystemMock.create(); const mockLogger = mockLoggerFactory.get('mock logger'); const createRegistry = (...types: Array>) => { @@ -572,7 +572,7 @@ describe('DocumentMigrator', () => { expect('Did not throw').toEqual('But it should have!'); } catch (error) { expect(error.message).toMatch(/Dang diggity!/); - const warning = loggingServiceMock.collect(mockLoggerFactory).warn[0][0]; + const warning = loggingSystemMock.collect(mockLoggerFactory).warn[0][0]; expect(warning).toContain(JSON.stringify(failedDoc)); expect(warning).toContain('dog:1.2.3'); } @@ -601,8 +601,8 @@ describe('DocumentMigrator', () => { migrationVersion: {}, }; migrator.migrate(doc); - expect(loggingServiceMock.collect(mockLoggerFactory).info[0][0]).toEqual(logTestMsg); - expect(loggingServiceMock.collect(mockLoggerFactory).warn[1][0]).toEqual(logTestMsg); + expect(loggingSystemMock.collect(mockLoggerFactory).info[0][0]).toEqual(logTestMsg); + expect(loggingSystemMock.collect(mockLoggerFactory).warn[1][0]).toEqual(logTestMsg); }); test('extracts the latest migration version info', () => { diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts index 392089c69f5a0..86c79cbfb5824 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts @@ -21,7 +21,7 @@ import _ from 'lodash'; import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { IndexMigrator } from './index_migrator'; -import { loggingServiceMock } from '../../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../../logging/logging_system.mock'; describe('IndexMigrator', () => { let testOpts: any; @@ -31,7 +31,7 @@ describe('IndexMigrator', () => { batchSize: 10, callCluster: jest.fn(), index: '.kibana', - log: loggingServiceMock.create().get(), + log: loggingSystemMock.create().get(), mappingProperties: {}, pollInterval: 1, scrollDuration: '1m', diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index 7a5c044924d0e..01b0d1cd0ba3a 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -19,7 +19,7 @@ import { take } from 'rxjs/operators'; import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; -import { loggingServiceMock } from '../../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectsType } from '../../types'; @@ -110,7 +110,7 @@ describe('KibanaMigrator', () => { function mockOptions(): KibanaMigratorOptions { const callCluster = jest.fn(); return { - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), kibanaVersion: '8.2.3', savedObjectValidations: {}, typeRegistry: createRegistry([ diff --git a/src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts b/src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts index 0fe07245dda20..8d021580da36c 100644 --- a/src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts @@ -20,7 +20,7 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerLogLegacyImportRoute } from '../log_legacy_import'; -import { loggingServiceMock } from '../../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { setupServer } from '../test_utils'; type setupServerReturn = UnwrapPromise>; @@ -28,11 +28,11 @@ type setupServerReturn = UnwrapPromise>; describe('POST /api/saved_objects/_log_legacy_import', () => { let server: setupServerReturn['server']; let httpSetup: setupServerReturn['httpSetup']; - let logger: ReturnType; + let logger: ReturnType; beforeEach(async () => { ({ server, httpSetup } = await setupServer()); - logger = loggingServiceMock.createLogger(); + logger = loggingSystemMock.createLogger(); const router = httpSetup.createRouter('/api/saved_objects/'); registerLogLegacyImportRoute(router, logger); @@ -50,7 +50,7 @@ describe('POST /api/saved_objects/_log_legacy_import', () => { .expect(200); expect(result.body).toEqual({ success: true }); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Importing saved objects from a .json file has been deprecated", diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 9dc3ac9b94d96..4d6316fceb568 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -388,6 +388,11 @@ export interface APICaller { (endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; } +// Warning: (ae-forgotten-export) The symbol "appendersSchema" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type AppenderConfigType = TypeOf; + // @public export function assertNever(x: never): never; @@ -574,6 +579,72 @@ export const config: { ignoreVersionMismatch: import("@kbn/config-schema/target/types/types").ConditionalType; }>; }; + logging: { + appenders: import("@kbn/config-schema").Type | Readonly<{ + pattern?: string | undefined; + highlight?: boolean | undefined; + } & { + kind: "pattern"; + }>; + kind: "console"; + }> | Readonly<{} & { + path: string; + layout: Readonly<{} & { + kind: "json"; + }> | Readonly<{ + pattern?: string | undefined; + highlight?: boolean | undefined; + } & { + kind: "pattern"; + }>; + kind: "file"; + }> | Readonly<{ + legacyLoggingConfig?: any; + } & { + kind: "legacy-appender"; + }>>; + loggers: import("@kbn/config-schema").ObjectType<{ + appenders: import("@kbn/config-schema").Type; + context: import("@kbn/config-schema").Type; + level: import("@kbn/config-schema").Type; + }>; + loggerContext: import("@kbn/config-schema").ObjectType<{ + appenders: import("@kbn/config-schema").Type | Readonly<{ + pattern?: string | undefined; + highlight?: boolean | undefined; + } & { + kind: "pattern"; + }>; + kind: "console"; + }> | Readonly<{} & { + path: string; + layout: Readonly<{} & { + kind: "json"; + }> | Readonly<{ + pattern?: string | undefined; + highlight?: boolean | undefined; + } & { + kind: "pattern"; + }>; + kind: "file"; + }> | Readonly<{ + legacyLoggingConfig?: any; + } & { + kind: "legacy-appender"; + }>>>; + loggers: import("@kbn/config-schema").Type[]>; + }>; + }; }; // @public @@ -639,6 +710,8 @@ export interface CoreSetup; + +// @public (undocumented) +export interface LoggerContextConfigInput { + // (undocumented) + appenders?: Record | Map; + // (undocumented) + loggers?: LoggerConfigType[]; +} + // @public export interface LoggerFactory { get(...contextParts: string[]): Logger; } +// @public +export interface LoggingServiceSetup { + configure(config$: Observable): void; +} + // @internal export class LogLevel { // (undocumented) diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index 5d535c9845724..e5e710d54e04b 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -91,3 +91,9 @@ export const mockStatusService = statusServiceMock.create(); jest.doMock('./status/status_service', () => ({ StatusService: jest.fn(() => mockStatusService), })); + +import { loggingServiceMock } from './logging/logging_service.mock'; +export const mockLoggingService = loggingServiceMock.create(); +jest.doMock('./logging/logging_service', () => ({ + LoggingService: jest.fn(() => mockLoggingService), +})); diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 1e3e1638cf2a0..1f507a85d3ddf 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -30,6 +30,7 @@ import { mockRenderingService, mockMetricsService, mockStatusService, + mockLoggingService, } from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; @@ -37,11 +38,11 @@ import { Env } from './config'; import { Server } from './server'; import { getEnvOptions } from './config/__mocks__/env'; -import { loggingServiceMock } from './logging/logging_service.mock'; +import { loggingSystemMock } from './logging/logging_system.mock'; import { rawConfigServiceMock } from './config/raw_config_service.mock'; const env = new Env('.', getEnvOptions()); -const logger = loggingServiceMock.create(); +const logger = loggingSystemMock.create(); const rawConfigService = rawConfigServiceMock.create({}); beforeEach(() => { @@ -68,6 +69,7 @@ test('sets up services on "setup"', async () => { expect(mockRenderingService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); expect(mockStatusService.setup).not.toHaveBeenCalled(); + expect(mockLoggingService.setup).not.toHaveBeenCalled(); await server.setup(); @@ -80,6 +82,7 @@ test('sets up services on "setup"', async () => { expect(mockRenderingService.setup).toHaveBeenCalledTimes(1); expect(mockMetricsService.setup).toHaveBeenCalledTimes(1); expect(mockStatusService.setup).toHaveBeenCalledTimes(1); + expect(mockLoggingService.setup).toHaveBeenCalledTimes(1); }); test('injects legacy dependency to context#setup()', async () => { @@ -151,6 +154,7 @@ test('stops services on "stop"', async () => { expect(mockUiSettingsService.stop).not.toHaveBeenCalled(); expect(mockMetricsService.stop).not.toHaveBeenCalled(); expect(mockStatusService.stop).not.toHaveBeenCalled(); + expect(mockLoggingService.stop).not.toHaveBeenCalled(); await server.stop(); @@ -162,6 +166,7 @@ test('stops services on "stop"', async () => { expect(mockUiSettingsService.stop).toHaveBeenCalledTimes(1); expect(mockMetricsService.stop).toHaveBeenCalledTimes(1); expect(mockStatusService.stop).toHaveBeenCalledTimes(1); + expect(mockLoggingService.stop).toHaveBeenCalledTimes(1); }); test(`doesn't setup core services if config validation fails`, async () => { @@ -179,6 +184,7 @@ test(`doesn't setup core services if config validation fails`, async () => { expect(mockRenderingService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); expect(mockStatusService.setup).not.toHaveBeenCalled(); + expect(mockLoggingService.setup).not.toHaveBeenCalled(); }); test(`doesn't setup core services if legacy config validation fails`, async () => { @@ -200,4 +206,5 @@ test(`doesn't setup core services if legacy config validation fails`, async () = expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); expect(mockStatusService.setup).not.toHaveBeenCalled(); + expect(mockLoggingService.setup).not.toHaveBeenCalled(); }); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index ae1a02cf71b88..3bbcd0e37e142 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -32,7 +32,7 @@ import { HttpService } from './http'; import { HttpResourcesService } from './http_resources'; import { RenderingService } from './rendering'; import { LegacyService, ensureValidConfiguration } from './legacy'; -import { Logger, LoggerFactory } from './logging'; +import { Logger, LoggerFactory, LoggingService, ILoggingSystem } from './logging'; import { UiSettingsService } from './ui_settings'; import { PluginsService, config as pluginsConfig } from './plugins'; import { SavedObjectsService } from '../server/saved_objects'; @@ -74,20 +74,23 @@ export class Server { private readonly metrics: MetricsService; private readonly httpResources: HttpResourcesService; private readonly status: StatusService; + private readonly logging: LoggingService; private readonly coreApp: CoreApp; #pluginsInitialized?: boolean; private coreStart?: InternalCoreStart; + private readonly logger: LoggerFactory; constructor( rawConfigProvider: RawConfigurationProvider, public readonly env: Env, - private readonly logger: LoggerFactory + private readonly loggingSystem: ILoggingSystem ) { + this.logger = this.loggingSystem.asLoggerFactory(); this.log = this.logger.get('server'); - this.configService = new ConfigService(rawConfigProvider, env, logger); + this.configService = new ConfigService(rawConfigProvider, env, this.logger); - const core = { coreId, configService: this.configService, env, logger }; + const core = { coreId, configService: this.configService, env, logger: this.logger }; this.context = new ContextService(core); this.http = new HttpService(core); this.rendering = new RenderingService(core); @@ -102,6 +105,7 @@ export class Server { this.status = new StatusService(core); this.coreApp = new CoreApp(core); this.httpResources = new HttpResourcesService(core); + this.logging = new LoggingService(core); } public async setup() { @@ -164,6 +168,10 @@ export class Server { savedObjects: savedObjectsSetup, }); + const loggingSetup = this.logging.setup({ + loggingSystem: this.loggingSystem, + }); + const coreSetup: InternalCoreSetup = { capabilities: capabilitiesSetup, context: contextServiceSetup, @@ -176,6 +184,7 @@ export class Server { metrics: metricsSetup, rendering: renderingSetup, httpResources: httpResourcesSetup, + logging: loggingSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -244,6 +253,7 @@ export class Server { await this.rendering.stop(); await this.metrics.stop(); await this.status.stop(); + await this.logging.stop(); } private registerCoreContext(coreSetup: InternalCoreSetup) { diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index 9c5a0625e8fd0..10a30db038174 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -21,7 +21,7 @@ import Chance from 'chance'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; import { savedObjectsClientMock } from '../../saved_objects/service/saved_objects_client.mock'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { getUpgradeableConfigMock } from './get_upgradeable_config.test.mock'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; @@ -35,7 +35,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () { const buildNum = chance.integer({ min: 1000, max: 5000 }); function setup() { - const logger = loggingServiceMock.create(); + const logger = loggingSystemMock.create(); const getUpgradeableConfig = getUpgradeableConfigMock; const savedObjectsClient = savedObjectsClientMock.create(); savedObjectsClient.create.mockImplementation( @@ -137,7 +137,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () { }); await run(); - expect(loggingServiceMock.collect(logger).debug).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(` Array [ Array [ "Upgrade config from 4.0.0 to 4.0.1", @@ -169,7 +169,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () { expect(error.message).toBe('foo'); } - expect(loggingServiceMock.collect(logger).debug).toHaveLength(0); + expect(loggingSystemMock.collect(logger).debug).toHaveLength(0); }); }); diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts index adf36e4491b79..d2e31dad58e55 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts @@ -26,10 +26,10 @@ import { TestUtils, } from '../../../../../test_utils/kbn_server'; import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config'; -import { loggingServiceMock } from '../../../logging/logging_service.mock'; +import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { httpServerMock } from '../../../http/http_server.mocks'; -const logger = loggingServiceMock.create().get(); +const logger = loggingSystemMock.create().get(); describe('createOrUpgradeSavedConfig()', () => { let savedObjectsClient: SavedObjectsClientContract; let servers: TestUtils; diff --git a/src/core/server/ui_settings/ui_settings_client.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts index 4ce33eed267a3..a38fb2ab7e06c 100644 --- a/src/core/server/ui_settings/ui_settings_client.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -20,7 +20,7 @@ import Chance from 'chance'; import { schema } from '@kbn/config-schema'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { createOrUpgradeSavedConfigMock } from './create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.mock'; import { SavedObjectsClient } from '../saved_objects'; @@ -28,7 +28,7 @@ import { savedObjectsClientMock } from '../saved_objects/service/saved_objects_c import { UiSettingsClient } from './ui_settings_client'; import { CannotOverrideError } from './ui_settings_errors'; -const logger = loggingServiceMock.create().get(); +const logger = loggingSystemMock.create().get(); const TYPE = 'config'; const ID = 'kibana-version'; @@ -375,7 +375,7 @@ describe('ui settings', () => { }, }); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Ignore invalid UiSettings value. Error: [validation [id]]: expected value of type [number] but got [string].", @@ -517,7 +517,7 @@ describe('ui settings', () => { user: 'foo', }); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Ignore invalid UiSettings value. Error: [validation [id]]: expected value of type [number] but got [string].", @@ -645,7 +645,7 @@ describe('ui settings', () => { expect(await uiSettings.get('id')).toBe(42); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Ignore invalid UiSettings value. Error: [validation [id]]: expected value of type [number] but got [string].", diff --git a/src/core/server/uuid/resolve_uuid.test.ts b/src/core/server/uuid/resolve_uuid.test.ts index eab027b532ddb..1a873a1cea0cf 100644 --- a/src/core/server/uuid/resolve_uuid.test.ts +++ b/src/core/server/uuid/resolve_uuid.test.ts @@ -21,7 +21,7 @@ import { join } from 'path'; import { readFile, writeFile } from './fs'; import { resolveInstanceUuid, UUID_7_6_0_BUG } from './resolve_uuid'; import { configServiceMock } from '../config/config_service.mock'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { BehaviorSubject } from 'rxjs'; import { Logger } from '../logging'; @@ -93,7 +93,7 @@ describe('resolveInstanceUuid', () => { mockReadFile({ uuid: DEFAULT_FILE_UUID }); mockWriteFile(); configService = getConfigService(DEFAULT_CONFIG_UUID); - logger = loggingServiceMock.create().get() as any; + logger = loggingSystemMock.create().get() as any; }); describe('when file is present and config property is set', () => { diff --git a/src/core/server/uuid/uuid_service.test.ts b/src/core/server/uuid/uuid_service.test.ts index a61061ff84263..092216303080e 100644 --- a/src/core/server/uuid/uuid_service.test.ts +++ b/src/core/server/uuid/uuid_service.test.ts @@ -21,7 +21,7 @@ import { UuidService } from './uuid_service'; import { resolveInstanceUuid } from './resolve_uuid'; import { CoreContext } from '../core_context'; -import { loggingServiceMock } from '../logging/logging_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { mockCoreContext } from '../core_context.mock'; import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; @@ -31,12 +31,12 @@ jest.mock('./resolve_uuid', () => ({ })); describe('UuidService', () => { - let logger: ReturnType; + let logger: ReturnType; let coreContext: CoreContext; beforeEach(() => { jest.clearAllMocks(); - logger = loggingServiceMock.create(); + logger = loggingSystemMock.create(); coreContext = mockCoreContext.create({ logger }); }); diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.test.ts b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts index 2b33489eb27c9..14ca45320b9c1 100644 --- a/src/plugins/share/server/routes/lib/short_url_lookup.test.ts +++ b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts @@ -20,7 +20,7 @@ import { shortUrlLookupProvider, ShortUrlLookupService, UrlAttributes } from './short_url_lookup'; import { SavedObjectsClientContract, SavedObject } from 'kibana/server'; -import { savedObjectsClientMock, loggingServiceMock } from '../../../../../core/server/mocks'; +import { savedObjectsClientMock, loggingSystemMock } from '../../../../../core/server/mocks'; describe('shortUrlLookupProvider', () => { const ID = 'bf00ad16941fc51420f91a93428b27a0'; @@ -35,7 +35,7 @@ describe('shortUrlLookupProvider', () => { savedObjects = savedObjectsClientMock.create(); savedObjects.create.mockResolvedValue({ id: ID } as SavedObject); deps = { savedObjects }; - shortUrl = shortUrlLookupProvider({ logger: loggingServiceMock.create().get() }); + shortUrl = shortUrlLookupProvider({ logger: loggingSystemMock.create().get() }); }); describe('generateUrlId', () => { diff --git a/src/plugins/usage_collection/server/collector/collector_set.test.ts b/src/plugins/usage_collection/server/collector/collector_set.test.ts index ced5206cee318..50919ecb3d83f 100644 --- a/src/plugins/usage_collection/server/collector/collector_set.test.ts +++ b/src/plugins/usage_collection/server/collector/collector_set.test.ts @@ -21,9 +21,9 @@ import { noop } from 'lodash'; import { Collector } from './collector'; import { CollectorSet } from './collector_set'; import { UsageCollector } from './usage_collector'; -import { loggingServiceMock } from '../../../../core/server/mocks'; +import { loggingSystemMock } from '../../../../core/server/mocks'; -const logger = loggingServiceMock.createLogger(); +const logger = loggingSystemMock.createLogger(); const loggerSpies = { debug: jest.spyOn(logger, 'debug'), diff --git a/src/plugins/usage_collection/server/mocks.ts b/src/plugins/usage_collection/server/mocks.ts index ca3710c62cd89..e1f13304165a1 100644 --- a/src/plugins/usage_collection/server/mocks.ts +++ b/src/plugins/usage_collection/server/mocks.ts @@ -17,14 +17,14 @@ * under the License. */ -import { loggingServiceMock } from '../../../core/server/mocks'; +import { loggingSystemMock } from '../../../core/server/mocks'; import { UsageCollectionSetup } from './plugin'; import { CollectorSet } from './collector'; const createSetupContract = () => { return { ...new CollectorSet({ - logger: loggingServiceMock.createLogger(), + logger: loggingSystemMock.createLogger(), maximumWaitTimeForAllCollectorsInS: 1, }), } as UsageCollectionSetup; diff --git a/test/plugin_functional/plugins/core_logging/kibana.json b/test/plugin_functional/plugins/core_logging/kibana.json new file mode 100644 index 0000000000000..3289c2c627b9a --- /dev/null +++ b/test/plugin_functional/plugins/core_logging/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "core_logging", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["core_logging"], + "server": true +} diff --git a/test/plugin_functional/plugins/core_logging/server/.gitignore b/test/plugin_functional/plugins/core_logging/server/.gitignore new file mode 100644 index 0000000000000..9a3d281179193 --- /dev/null +++ b/test/plugin_functional/plugins/core_logging/server/.gitignore @@ -0,0 +1 @@ +/*debug.log diff --git a/test/plugin_functional/plugins/core_logging/server/index.ts b/test/plugin_functional/plugins/core_logging/server/index.ts new file mode 100644 index 0000000000000..ca1d9da95b495 --- /dev/null +++ b/test/plugin_functional/plugins/core_logging/server/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { PluginInitializerContext } from '../../../../../src/core/server'; +import { CoreLoggingPlugin } from './plugin'; + +export const plugin = (init: PluginInitializerContext) => new CoreLoggingPlugin(init); diff --git a/test/plugin_functional/plugins/core_logging/server/plugin.ts b/test/plugin_functional/plugins/core_logging/server/plugin.ts new file mode 100644 index 0000000000000..a7820a0f67525 --- /dev/null +++ b/test/plugin_functional/plugins/core_logging/server/plugin.ts @@ -0,0 +1,118 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; +import { Subject } from 'rxjs'; +import { schema } from '@kbn/config-schema'; +import type { + PluginInitializerContext, + Plugin, + CoreSetup, + LoggerContextConfigInput, + Logger, +} from '../../../../../src/core/server'; + +const CUSTOM_LOGGING_CONFIG: LoggerContextConfigInput = { + appenders: { + customJsonFile: { + kind: 'file', + path: resolve(__dirname, 'json_debug.log'), // use 'debug.log' suffix so file watcher does not restart server + layout: { + kind: 'json', + }, + }, + customPatternFile: { + kind: 'file', + path: resolve(__dirname, 'pattern_debug.log'), + layout: { + kind: 'pattern', + pattern: 'CUSTOM - PATTERN [%logger][%level] %message', + }, + }, + }, + + loggers: [ + { context: 'debug_json', appenders: ['customJsonFile'], level: 'debug' }, + { context: 'debug_pattern', appenders: ['customPatternFile'], level: 'debug' }, + { context: 'info_json', appenders: ['customJsonFile'], level: 'info' }, + { context: 'info_pattern', appenders: ['customPatternFile'], level: 'info' }, + { context: 'all', appenders: ['customJsonFile', 'customPatternFile'], level: 'debug' }, + ], +}; + +export class CoreLoggingPlugin implements Plugin { + private readonly logger: Logger; + + constructor(init: PluginInitializerContext) { + this.logger = init.logger.get(); + } + + public setup(core: CoreSetup) { + const loggingConfig$ = new Subject(); + core.logging.configure(loggingConfig$); + + const router = core.http.createRouter(); + + // Expose a route that allows our test suite to write logs as this plugin + router.post( + { + path: '/internal/core-logging/write-log', + validate: { + body: schema.object({ + level: schema.oneOf([schema.literal('debug'), schema.literal('info')]), + message: schema.string(), + context: schema.arrayOf(schema.string()), + }), + }, + }, + (ctx, req, res) => { + const { level, message, context } = req.body; + const logger = this.logger.get(...context); + + if (level === 'debug') { + logger.debug(message); + } else if (level === 'info') { + logger.info(message); + } + + return res.ok(); + } + ); + + // Expose a route to toggle on and off the custom config + router.post( + { + path: '/internal/core-logging/update-config', + validate: { body: schema.object({ enableCustomConfig: schema.boolean() }) }, + }, + (ctx, req, res) => { + if (req.body.enableCustomConfig) { + loggingConfig$.next(CUSTOM_LOGGING_CONFIG); + } else { + loggingConfig$.next({}); + } + + return res.ok({ body: `Updated config: ${req.body.enableCustomConfig}` }); + } + ); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/core_logging/tsconfig.json b/test/plugin_functional/plugins/core_logging/tsconfig.json new file mode 100644 index 0000000000000..7389eb6ce159b --- /dev/null +++ b/test/plugin_functional/plugins/core_logging/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/test_suites/core_plugins/index.ts b/test/plugin_functional/test_suites/core_plugins/index.ts index 8f54ec6c0f4cd..8f7c2267d34b4 100644 --- a/test/plugin_functional/test_suites/core_plugins/index.ts +++ b/test/plugin_functional/test_suites/core_plugins/index.ts @@ -30,5 +30,6 @@ export default function ({ loadTestFile }: PluginFunctionalProviderContext) { loadTestFile(require.resolve('./application_leave_confirm')); loadTestFile(require.resolve('./application_status')); loadTestFile(require.resolve('./rendering')); + loadTestFile(require.resolve('./logging')); }); } diff --git a/test/plugin_functional/test_suites/core_plugins/logging.ts b/test/plugin_functional/test_suites/core_plugins/logging.ts new file mode 100644 index 0000000000000..9fdaa6ce834ea --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/logging.ts @@ -0,0 +1,146 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; +import fs from 'fs'; +import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); + + describe('plugin logging', function describeIndexTests() { + const LOG_FILE_DIRECTORY = resolve(__dirname, '..', '..', 'plugins', 'core_logging', 'server'); + const JSON_FILE_PATH = resolve(LOG_FILE_DIRECTORY, 'json_debug.log'); + const PATTERN_FILE_PATH = resolve(LOG_FILE_DIRECTORY, 'pattern_debug.log'); + + beforeEach(async () => { + // "touch" each file to ensure it exists and is empty before each test + await fs.promises.writeFile(JSON_FILE_PATH, ''); + await fs.promises.writeFile(PATTERN_FILE_PATH, ''); + }); + + async function readLines(path: string) { + const contents = await fs.promises.readFile(path, { encoding: 'utf8' }); + return contents.trim().split('\n'); + } + + async function readJsonLines() { + return (await readLines(JSON_FILE_PATH)) + .filter((line) => line.length > 0) + .map((line) => JSON.parse(line)) + .map(({ level, message, context }) => ({ level, message, context })); + } + + function writeLog(context: string[], level: string, message: string) { + return supertest + .post('/internal/core-logging/write-log') + .set('kbn-xsrf', 'anything') + .send({ context, level, message }) + .expect(200); + } + + function setContextConfig(enable: boolean) { + return supertest + .post('/internal/core-logging/update-config') + .set('kbn-xsrf', 'anything') + .send({ enableCustomConfig: enable }) + .expect(200); + } + + it('does not write to custom appenders when not configured', async () => { + await setContextConfig(false); + await writeLog(['debug_json'], 'info', 'i go to the default appender!'); + expect(await readJsonLines()).to.eql([]); + }); + + it('writes debug_json context to custom JSON appender', async () => { + await setContextConfig(true); + await writeLog(['debug_json'], 'debug', 'log1'); + await writeLog(['debug_json'], 'info', 'log2'); + expect(await readJsonLines()).to.eql([ + { + level: 'DEBUG', + context: 'plugins.core_logging.debug_json', + message: 'log1', + }, + { + level: 'INFO', + context: 'plugins.core_logging.debug_json', + message: 'log2', + }, + ]); + }); + + it('writes info_json context to custom JSON appender', async () => { + await setContextConfig(true); + await writeLog(['info_json'], 'debug', 'i should not be logged!'); + await writeLog(['info_json'], 'info', 'log2'); + expect(await readJsonLines()).to.eql([ + { + level: 'INFO', + context: 'plugins.core_logging.info_json', + message: 'log2', + }, + ]); + }); + + it('writes debug_pattern context to custom pattern appender', async () => { + await setContextConfig(true); + await writeLog(['debug_pattern'], 'debug', 'log1'); + await writeLog(['debug_pattern'], 'info', 'log2'); + expect(await readLines(PATTERN_FILE_PATH)).to.eql([ + 'CUSTOM - PATTERN [plugins.core_logging.debug_pattern][DEBUG] log1', + 'CUSTOM - PATTERN [plugins.core_logging.debug_pattern][INFO ] log2', + ]); + }); + + it('writes info_pattern context to custom pattern appender', async () => { + await setContextConfig(true); + await writeLog(['info_pattern'], 'debug', 'i should not be logged!'); + await writeLog(['info_pattern'], 'info', 'log2'); + expect(await readLines(PATTERN_FILE_PATH)).to.eql([ + 'CUSTOM - PATTERN [plugins.core_logging.info_pattern][INFO ] log2', + ]); + }); + + it('writes all context to both appenders', async () => { + await setContextConfig(true); + await writeLog(['all'], 'debug', 'log1'); + await writeLog(['all'], 'info', 'log2'); + expect(await readJsonLines()).to.eql([ + { + level: 'DEBUG', + context: 'plugins.core_logging.all', + message: 'log1', + }, + { + level: 'INFO', + context: 'plugins.core_logging.all', + message: 'log2', + }, + ]); + expect(await readLines(PATTERN_FILE_PATH)).to.eql([ + 'CUSTOM - PATTERN [plugins.core_logging.all][DEBUG] log1', + 'CUSTOM - PATTERN [plugins.core_logging.all][INFO ] log2', + ]); + }); + }); +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts index 459ffd7667f15..21efc05d49c38 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts @@ -9,7 +9,7 @@ import { ActionTypeRegistry } from '../action_type_registry'; import { taskManagerMock } from '../../../task_manager/server/task_manager.mock'; import { registerBuiltInActionTypes } from './index'; import { Logger } from '../../../../../src/core/server'; -import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../src/core/server/mocks'; import { actionsConfigMock } from '../actions_config.mock'; import { licenseStateMock } from '../lib/license_state.mock'; @@ -19,7 +19,7 @@ export function createActionTypeRegistry(): { logger: jest.Mocked; actionTypeRegistry: ActionTypeRegistry; } { - const logger = loggingServiceMock.create().get() as jest.Mocked; + const logger = loggingSystemMock.create().get() as jest.Mocked; const actionTypeRegistry = new ActionTypeRegistry({ taskManager: taskManagerMock.setup(), taskRunnerFactory: new TaskRunnerFactory( diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index a02f79a49e8e2..3514bd4257b0f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -10,14 +10,14 @@ jest.mock('nodemailer', () => ({ import { Logger } from '../../../../../../src/core/server'; import { sendEmail } from './send_email'; -import { loggingServiceMock } from '../../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import nodemailer from 'nodemailer'; const createTransportMock = nodemailer.createTransport as jest.Mock; const sendMailMockResult = { result: 'does not matter' }; const sendMailMock = jest.fn(); -const mockLogger = loggingServiceMock.create().get() as jest.Mocked; +const mockLogger = loggingSystemMock.create().get() as jest.Mocked; describe('send_email module', () => { beforeEach(() => { diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 88216c4bf13ad..c8e6669275e11 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { ActionExecutor } from './action_executor'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; -import { loggingServiceMock, savedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import { loggingSystemMock, savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { eventLoggerMock } from '../../../event_log/server/mocks'; import { spacesServiceMock } from '../../../spaces/server/spaces_service/spaces_service.mock'; import { ActionType } from '../types'; @@ -31,7 +31,7 @@ const executeParams = { const spacesMock = spacesServiceMock.createSetupContract(); actionExecutor.initialize({ - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), spaces: spacesMock, getServices: () => services, getScopedSavedObjectsClient: () => savedObjectsClientWithHidden, @@ -266,7 +266,7 @@ test('should not throws an error if actionType is preconfigured', async () => { test('throws an error when passing isESOUsingEphemeralEncryptionKey with value of true', async () => { const customActionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: true }); customActionExecutor.initialize({ - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), spaces: spacesMock, getScopedSavedObjectsClient: () => savedObjectsClientWithHidden, getServices: () => services, diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 3339416a1112d..06cb84ad79a89 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -12,7 +12,7 @@ import { TaskRunnerFactory } from './task_runner_factory'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { actionExecutorMock } from './action_executor.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; -import { savedObjectsClientMock, loggingServiceMock } from 'src/core/server/mocks'; +import { savedObjectsClientMock, loggingSystemMock } from 'src/core/server/mocks'; import { eventLoggerMock } from '../../../event_log/server/mocks'; import { ActionTypeDisabledError } from './errors'; @@ -56,7 +56,7 @@ const services = { savedObjectsClient: savedObjectsClientMock.create(), }; const actionExecutorInitializerParams = { - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), getServices: jest.fn().mockReturnValue(services), actionTypeRegistry, getScopedSavedObjectsClient: () => savedObjectsClientMock.create(), @@ -67,7 +67,7 @@ const actionExecutorInitializerParams = { const taskRunnerFactoryInitializerParams = { spaceIdToNamespace, actionTypeRegistry, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: mockedEncryptedSavedObjectsClient, getBasePath: jest.fn().mockReturnValue(undefined), getScopedSavedObjectsClient: jest.fn().mockReturnValue(services.savedObjectsClient), diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts index 315e4800d4c73..d3583fd4cdb0b 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { loggingServiceMock } from '../../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { getAlertType } from './alert_type'; import { Params } from './alert_type_params'; @@ -13,7 +13,7 @@ describe('alertType', () => { indexThreshold: { timeSeriesQuery: jest.fn(), }, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }; const alertType = getAlertType(service); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.test.ts index d40df4c91998f..0565a8634fc71 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_query.test.ts @@ -6,7 +6,7 @@ // test error conditions of calling timeSeriesQuery - postive results tested in FT -import { loggingServiceMock } from '../../../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../../../src/core/server/mocks'; import { coreMock } from '../../../../../../../src/core/server/mocks'; import { AlertingBuiltinsPlugin } from '../../../plugin'; import { TimeSeriesQueryParameters, TimeSeriesResult, TimeSeriesQuery } from './time_series_query'; @@ -44,7 +44,7 @@ describe('timeSeriesQuery', () => { mockCallCluster.mockReset(); params = { - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), callCluster: mockCallCluster, query: DefaultQueryParams, }; diff --git a/x-pack/plugins/alerts/server/alerts_client.test.ts b/x-pack/plugins/alerts/server/alerts_client.test.ts index f494f1358980d..d69d04f71ce9e 100644 --- a/x-pack/plugins/alerts/server/alerts_client.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client.test.ts @@ -6,7 +6,7 @@ import uuid from 'uuid'; import { schema } from '@kbn/config-schema'; import { AlertsClient, CreateOptions } from './alerts_client'; -import { savedObjectsClientMock, loggingServiceMock } from '../../../../src/core/server/mocks'; +import { savedObjectsClientMock, loggingSystemMock } from '../../../../src/core/server/mocks'; import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; import { TaskStatus } from '../../task_manager/server'; @@ -29,7 +29,7 @@ const alertsClientParams = { getUserName: jest.fn(), createAPIKey: jest.fn(), invalidateAPIKey: jest.fn(), - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), }; diff --git a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts index a2d64c94ce007..128d54c10b66a 100644 --- a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts @@ -9,7 +9,7 @@ import { AlertsClientFactory, AlertsClientFactoryOpts } from './alerts_client_fa import { alertTypeRegistryMock } from './alert_type_registry.mock'; import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { KibanaRequest } from '../../../../src/core/server'; -import { loggingServiceMock, savedObjectsClientMock } from '../../../../src/core/server/mocks'; +import { loggingSystemMock, savedObjectsClientMock } from '../../../../src/core/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { AuthenticatedUser } from '../../../plugins/security/common/model'; import { securityMock } from '../../security/server/mocks'; @@ -20,7 +20,7 @@ jest.mock('./alerts_client'); const savedObjectsClient = savedObjectsClientMock.create(); const securityPluginSetup = securityMock.createSetup(); const alertsClientFactoryParams: jest.Mocked = { - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), taskManager: taskManagerMock.start(), alertTypeRegistry: alertTypeRegistryMock.create(), getSpaceId: jest.fn(), diff --git a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts index dd5a9f531bd58..3b1948c5e7ad7 100644 --- a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts @@ -6,7 +6,7 @@ import { AlertType } from '../types'; import { createExecutionHandler } from './create_execution_handler'; -import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../src/core/server/mocks'; import { actionsMock, actionsClientMock } from '../../../actions/server/mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { KibanaRequest } from 'kibana/server'; @@ -34,7 +34,7 @@ const createExecutionHandlerParams = { spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), alertType, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), eventLogger: eventLoggerMock.create(), actions: [ { diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 690971bc87006..7a031c6671fd0 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -11,7 +11,7 @@ import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { TaskRunnerContext } from './task_runner_factory'; import { TaskRunner } from './task_runner'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; -import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../src/core/server/mocks'; import { PluginStartContract as ActionsPluginStart } from '../../../actions/server'; import { actionsMock, actionsClientMock } from '../../../actions/server/mocks'; import { alertsMock } from '../mocks'; @@ -66,7 +66,7 @@ describe('Task Runner', () => { getServices: jest.fn().mockReturnValue(services), actionsPlugin: actionsMock.createStart(), encryptedSavedObjectsClient, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), eventLogger: eventLoggerMock.create(), diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts index 7d9710d8a3e08..8f3e44b1cf42d 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts @@ -8,7 +8,7 @@ import sinon from 'sinon'; import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; -import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../src/core/server/mocks'; import { actionsMock } from '../../../actions/server/mocks'; import { alertsMock } from '../mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; @@ -57,7 +57,7 @@ describe('Task Runner Factory', () => { getServices: jest.fn().mockReturnValue(services), actionsPlugin: actionsMock.createStart(), encryptedSavedObjectsClient: encryptedSavedObjectsPlugin.getClient(), - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), eventLogger: eventLoggerMock.create(), diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts index db0417434227c..290175d9062ea 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts @@ -9,7 +9,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; import { CUSTOM_ELEMENT_TYPE } from '../../../common/lib/constants'; import { initializeCreateCustomElementRoute } from './create'; @@ -41,7 +41,7 @@ describe('POST custom element', () => { const router = httpService.createRouter(); initializeCreateCustomElementRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.post.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts index 98b26ec368ab1..62ce4b9c3593c 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts @@ -11,7 +11,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; const mockRouteContext = ({ @@ -30,7 +30,7 @@ describe('DELETE custom element', () => { const router = httpService.createRouter(); initializeDeleteCustomElementRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.delete.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts index dead9ded8a14a..d42c97b62e0f3 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts @@ -10,7 +10,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; const mockRouteContext = ({ @@ -29,7 +29,7 @@ describe('Find custom element', () => { const router = httpService.createRouter(); initializeFindCustomElementsRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.get.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts index 09b620aeff9bb..7b4d0eba37419 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts @@ -11,7 +11,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; const mockRouteContext = ({ @@ -30,7 +30,7 @@ describe('GET custom element', () => { const router = httpService.createRouter(); initializeGetCustomElementRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.get.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts index 19477458bacb5..0f954904355ae 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts @@ -13,7 +13,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; import { okResponse } from '../ok_response'; @@ -55,7 +55,7 @@ describe('PUT custom element', () => { const router = httpService.createRouter(); initializeUpdateCustomElementRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.put.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts index 93fdb4304acc6..c1918feb7f4ec 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts @@ -9,7 +9,7 @@ import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'sr import { httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, elasticsearchServiceMock, } from 'src/core/server/mocks'; @@ -29,7 +29,7 @@ describe('Retrieve ES Fields', () => { const router = httpService.createRouter(); initializeESFieldsRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.get.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/shareables/download.test.ts b/x-pack/plugins/canvas/server/routes/shareables/download.test.ts index 75eeb46c890d5..0267a695ae9fe 100644 --- a/x-pack/plugins/canvas/server/routes/shareables/download.test.ts +++ b/x-pack/plugins/canvas/server/routes/shareables/download.test.ts @@ -8,7 +8,7 @@ jest.mock('fs'); import fs from 'fs'; import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server'; -import { httpServiceMock, httpServerMock, loggingServiceMock } from 'src/core/server/mocks'; +import { httpServiceMock, httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; import { initializeDownloadShareableWorkpadRoute } from './download'; const mockRouteContext = {} as RequestHandlerContext; @@ -23,7 +23,7 @@ describe('Download Canvas shareables runtime', () => { const router = httpService.createRouter(); initializeDownloadShareableWorkpadRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.get.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts b/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts index 5a2d122c2754b..29dcb4268e618 100644 --- a/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts +++ b/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts @@ -8,7 +8,7 @@ jest.mock('archiver'); const archiver = require('archiver') as jest.Mock; import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server'; -import { httpServiceMock, httpServerMock, loggingServiceMock } from 'src/core/server/mocks'; +import { httpServiceMock, httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; import { initializeZipShareableWorkpadRoute } from './zip'; import { API_ROUTE_SHAREABLE_ZIP } from '../../../common/lib'; import { @@ -29,7 +29,7 @@ describe('Zips Canvas shareables runtime together with workpad', () => { const router = httpService.createRouter(); initializeZipShareableWorkpadRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.post.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/workpad/create.test.ts b/x-pack/plugins/canvas/server/routes/workpad/create.test.ts index 2ed63e7397108..9cadb50b9a506 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/create.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/create.test.ts @@ -9,7 +9,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; import { CANVAS_TYPE } from '../../../common/lib/constants'; import { initializeCreateWorkpadRoute } from './create'; @@ -41,7 +41,7 @@ describe('POST workpad', () => { const router = httpService.createRouter(); initializeCreateWorkpadRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.post.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts b/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts index 712ff29400382..32ce30325b60a 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts @@ -11,7 +11,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; const mockRouteContext = ({ @@ -30,7 +30,7 @@ describe('DELETE workpad', () => { const router = httpService.createRouter(); initializeDeleteWorkpadRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.delete.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/workpad/find.test.ts b/x-pack/plugins/canvas/server/routes/workpad/find.test.ts index e2dd8552379b7..a87cf7be57d81 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/find.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/find.test.ts @@ -10,7 +10,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; const mockRouteContext = ({ @@ -29,7 +29,7 @@ describe('Find workpad', () => { const router = httpService.createRouter(); initializeFindWorkpadsRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.get.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/workpad/get.test.ts b/x-pack/plugins/canvas/server/routes/workpad/get.test.ts index 9ecd9ceefed8d..8cc190dc6231c 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/get.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/get.test.ts @@ -11,7 +11,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; import { workpadWithGroupAsElement } from '../../../__tests__/fixtures/workpads'; import { CanvasWorkpad } from '../../../types'; @@ -32,7 +32,7 @@ describe('GET workpad', () => { const router = httpService.createRouter(); initializeGetWorkpadRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.get.mock.calls[0][1]; diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts index 36ea984447d8a..6d7ea06852a5e 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts @@ -12,7 +12,7 @@ import { savedObjectsClientMock, httpServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, } from 'src/core/server/mocks'; import { workpads } from '../../../__tests__/fixtures/workpads'; import { okResponse } from '../ok_response'; @@ -42,7 +42,7 @@ describe('PUT workpad', () => { const router = httpService.createRouter(); initializeUpdateWorkpadRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.put.mock.calls[0][1]; @@ -156,7 +156,7 @@ describe('update assets', () => { const router = httpService.createRouter(); initializeUpdateWorkpadAssetsRoute({ router, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }); routeHandler = router.put.mock.calls[0][1]; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts index e00c1c111b41b..8fde66ea82019 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { loggingServiceMock, httpServiceMock } from '../../../../../../../src/core/server/mocks'; +import { loggingSystemMock, httpServiceMock } from '../../../../../../../src/core/server/mocks'; import { CaseService, CaseConfigureService } from '../../../services'; import { authenticationMock } from '../__fixtures__'; import { RouteDeps } from '../types'; @@ -17,7 +17,7 @@ export const createRoute = async ( const httpService = httpServiceMock.createSetupContract(); const router = httpService.createRouter(); - const log = loggingServiceMock.create().get('case'); + const log = loggingSystemMock.create().get('case'); const caseServicePlugin = new CaseService(log); const caseConfigureServicePlugin = new CaseConfigureService(log); diff --git a/x-pack/plugins/encrypted_saved_objects/server/config.test.ts b/x-pack/plugins/encrypted_saved_objects/server/config.test.ts index db07f0f9ce2c0..3f8074eb15c0c 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/config.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/config.test.ts @@ -7,7 +7,7 @@ jest.mock('crypto', () => ({ randomBytes: jest.fn() })); import { first } from 'rxjs/operators'; -import { loggingServiceMock, coreMock } from 'src/core/server/mocks'; +import { loggingSystemMock, coreMock } from 'src/core/server/mocks'; import { createConfig$, ConfigSchema } from './config'; describe('config schema', () => { @@ -60,7 +60,7 @@ describe('createConfig$()', () => { usingEphemeralEncryptionKey: true, }); - expect(loggingServiceMock.collect(contextMock.logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(contextMock.logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Generating a random key for xpack.encryptedSavedObjects.encryptionKey. To be able to decrypt encrypted saved objects attributes after restart, please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml", @@ -79,6 +79,6 @@ describe('createConfig$()', () => { usingEphemeralEncryptionKey: false, }); - expect(loggingServiceMock.collect(contextMock.logger).warn).toEqual([]); + expect(loggingSystemMock.collect(contextMock.logger).warn).toEqual([]); }); }); diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts index 6ece9d1be8ec8..db7c96f83dff2 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts @@ -12,7 +12,7 @@ import { EncryptedSavedObjectsAuditLogger } from '../audit'; import { EncryptedSavedObjectsService } from './encrypted_saved_objects_service'; import { EncryptionError } from './encryption_error'; -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { encryptedSavedObjectsAuditLoggerMock } from '../audit/index.mock'; let service: EncryptedSavedObjectsService; @@ -28,7 +28,7 @@ beforeEach(() => { service = new EncryptedSavedObjectsService( 'encryption-key-abc', - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), mockAuditLogger ); }); @@ -222,7 +222,7 @@ describe('#encryptAttributes', () => { service = new EncryptedSavedObjectsService( 'encryption-key-abc', - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), mockAuditLogger ); }); @@ -916,7 +916,7 @@ describe('#decryptAttributes', () => { it('fails if encrypted with another encryption key', async () => { service = new EncryptedSavedObjectsService( 'encryption-key-abc*', - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), mockAuditLogger ); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index ada86adf84cfd..459a2cc65671e 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -5,7 +5,7 @@ */ import { ClusterClient, Logger } from '../../../../../src/core/server'; -import { elasticsearchServiceMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { elasticsearchServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; import { ClusterClientAdapter, IClusterClientAdapter } from './cluster_client_adapter'; import moment from 'moment'; import { findOptionsSchema } from '../event_log_client'; @@ -17,7 +17,7 @@ let clusterClient: EsClusterClient; let clusterClientAdapter: IClusterClientAdapter; beforeEach(() => { - logger = loggingServiceMock.createLogger(); + logger = loggingSystemMock.createLogger(); clusterClient = elasticsearchServiceMock.createClusterClient(); clusterClientAdapter = new ClusterClientAdapter({ logger, diff --git a/x-pack/plugins/event_log/server/es/context.mock.ts b/x-pack/plugins/event_log/server/es/context.mock.ts index c15fee803fb71..0c9f7b29b6411 100644 --- a/x-pack/plugins/event_log/server/es/context.mock.ts +++ b/x-pack/plugins/event_log/server/es/context.mock.ts @@ -7,14 +7,14 @@ import { EsContext } from './context'; import { namesMock } from './names.mock'; import { IClusterClientAdapter } from './cluster_client_adapter'; -import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../src/core/server/mocks'; import { clusterClientAdapterMock } from './cluster_client_adapter.mock'; const createContextMock = () => { const mock: jest.Mocked & { esAdapter: jest.Mocked; } = { - logger: loggingServiceMock.createLogger(), + logger: loggingSystemMock.createLogger(), esNames: namesMock.create(), initialize: jest.fn(), waitTillReady: jest.fn(), diff --git a/x-pack/plugins/event_log/server/es/context.test.ts b/x-pack/plugins/event_log/server/es/context.test.ts index 09fe676a5762e..6f9ee5875ddb7 100644 --- a/x-pack/plugins/event_log/server/es/context.test.ts +++ b/x-pack/plugins/event_log/server/es/context.test.ts @@ -6,7 +6,7 @@ import { createEsContext } from './context'; import { ClusterClient, Logger } from '../../../../../src/core/server'; -import { elasticsearchServiceMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { elasticsearchServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; jest.mock('../lib/../../../../package.json', () => ({ version: '1.2.3', })); @@ -16,7 +16,7 @@ let logger: Logger; let clusterClient: EsClusterClient; beforeEach(() => { - logger = loggingServiceMock.createLogger(); + logger = loggingSystemMock.createLogger(); clusterClient = elasticsearchServiceMock.createClusterClient(); }); diff --git a/x-pack/plugins/event_log/server/event_log_service.test.ts b/x-pack/plugins/event_log/server/event_log_service.test.ts index 43883ea4e384c..2cf68592f2fa1 100644 --- a/x-pack/plugins/event_log/server/event_log_service.test.ts +++ b/x-pack/plugins/event_log/server/event_log_service.test.ts @@ -7,9 +7,9 @@ import { IEventLogConfig } from './types'; import { EventLogService } from './event_log_service'; import { contextMock } from './es/context.mock'; -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; -const loggingService = loggingServiceMock.create(); +const loggingService = loggingSystemMock.create(); const systemLogger = loggingService.get(); describe('EventLogService', () => { diff --git a/x-pack/plugins/event_log/server/event_logger.test.ts b/x-pack/plugins/event_log/server/event_logger.test.ts index 2bda194a65d13..d4d3df3ef8267 100644 --- a/x-pack/plugins/event_log/server/event_logger.test.ts +++ b/x-pack/plugins/event_log/server/event_logger.test.ts @@ -9,20 +9,20 @@ import { ECS_VERSION } from './types'; import { EventLogService } from './event_log_service'; import { EsContext } from './es/context'; import { contextMock } from './es/context.mock'; -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { delay } from './lib/delay'; import { EVENT_LOGGED_PREFIX } from './event_logger'; const KIBANA_SERVER_UUID = '424-24-2424'; describe('EventLogger', () => { - let systemLogger: ReturnType; + let systemLogger: ReturnType; let esContext: EsContext; let service: IEventLogService; let eventLogger: IEventLogger; beforeEach(() => { - systemLogger = loggingServiceMock.createLogger(); + systemLogger = loggingSystemMock.createLogger(); esContext = contextMock.create(); service = new EventLogService({ esContext, @@ -183,7 +183,7 @@ describe('EventLogger', () => { // return the next logged event; throw if not an event async function waitForLogEvent( - mockLogger: ReturnType, + mockLogger: ReturnType, waitSeconds: number = 1 ): Promise { const result = await waitForLog(mockLogger, waitSeconds); @@ -193,7 +193,7 @@ async function waitForLogEvent( // return the next logged message; throw if it is an event async function waitForLogMessage( - mockLogger: ReturnType, + mockLogger: ReturnType, waitSeconds: number = 1 ): Promise { const result = await waitForLog(mockLogger, waitSeconds); @@ -203,7 +203,7 @@ async function waitForLogMessage( // return the next logged message, if it's an event log entry, parse it async function waitForLog( - mockLogger: ReturnType, + mockLogger: ReturnType, waitSeconds: number = 1 ): Promise { const intervals = 4; diff --git a/x-pack/plugins/event_log/server/lib/bounded_queue.test.ts b/x-pack/plugins/event_log/server/lib/bounded_queue.test.ts index dd6d15a6e4843..b30d83f24f261 100644 --- a/x-pack/plugins/event_log/server/lib/bounded_queue.test.ts +++ b/x-pack/plugins/event_log/server/lib/bounded_queue.test.ts @@ -5,9 +5,9 @@ */ import { createBoundedQueue } from './bounded_queue'; -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; -const loggingService = loggingServiceMock.create(); +const loggingService = loggingSystemMock.create(); const logger = loggingService.get(); describe('basic', () => { diff --git a/x-pack/plugins/licensing/server/plugin.test.ts b/x-pack/plugins/licensing/server/plugin.test.ts index 3e31eae945383..9d76472b51cd2 100644 --- a/x-pack/plugins/licensing/server/plugin.test.ts +++ b/x-pack/plugins/licensing/server/plugin.test.ts @@ -12,7 +12,7 @@ import { LicensingPlugin } from './plugin'; import { coreMock, elasticsearchServiceMock, - loggingServiceMock, + loggingSystemMock, } from '../../../../src/core/server/mocks'; import { IClusterClient } from '../../../../src/core/server/'; @@ -173,7 +173,7 @@ describe('licensing plugin', () => { await flushPromises(); - const loggedMessages = loggingServiceMock.collect(pluginInitContextMock.logger).debug; + const loggedMessages = loggingSystemMock.collect(pluginInitContextMock.logger).debug; expect( loggedMessages.some(([message]) => diff --git a/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.test.ts index 2ddb4a5d5b994..b00233137943d 100644 --- a/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/lib/screenshots/observable.test.ts @@ -17,7 +17,7 @@ jest.mock('../../../../browsers/chromium/puppeteer', () => ({ import * as Rx from 'rxjs'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { loggingServiceMock } from '../../../../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks'; import { HeadlessChromiumDriver } from '../../../../browsers'; import { LevelLogger } from '../../../../lib'; import { createMockBrowserDriverFactory, createMockLayoutInstance } from '../../../../test_helpers'; @@ -28,7 +28,7 @@ import { screenshotsObservableFactory } from './observable'; /* * Mocks */ -const mockLogger = jest.fn(loggingServiceMock.create); +const mockLogger = jest.fn(loggingSystemMock.create); const logger = new LevelLogger(mockLogger()); const mockConfig = { timeouts: { openUrl: 13 } } as CaptureConfig; diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts index 94a2ada8df1da..b2d866d07ff89 100644 --- a/x-pack/plugins/security/server/audit/audit_service.test.ts +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { AuditService } from './audit_service'; -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { licenseMock } from '../../common/licensing/index.mock'; import { ConfigSchema, ConfigType } from '../config'; import { SecurityLicenseFeatures } from '../../common/licensing'; @@ -20,7 +20,7 @@ const config = createConfig({ describe('#setup', () => { it('returns the expected contract', () => { - const logger = loggingServiceMock.createLogger(); + const logger = loggingSystemMock.createLogger(); const auditService = new AuditService(logger); const license = licenseMock.create(); expect(auditService.setup({ license, config })).toMatchInlineSnapshot(` @@ -34,7 +34,7 @@ describe('#setup', () => { test(`calls the underlying logger with the provided message and requisite tags`, () => { const pluginId = 'foo'; - const logger = loggingServiceMock.createLogger(); + const logger = loggingSystemMock.createLogger(); const license = licenseMock.create(); license.features$ = new BehaviorSubject({ allowAuditLogging: true, @@ -58,7 +58,7 @@ test(`calls the underlying logger with the provided message and requisite tags`, test(`calls the underlying logger with the provided metadata`, () => { const pluginId = 'foo'; - const logger = loggingServiceMock.createLogger(); + const logger = loggingSystemMock.createLogger(); const license = licenseMock.create(); license.features$ = new BehaviorSubject({ allowAuditLogging: true, @@ -90,7 +90,7 @@ test(`calls the underlying logger with the provided metadata`, () => { test(`does not call the underlying logger if license does not support audit logging`, () => { const pluginId = 'foo'; - const logger = loggingServiceMock.createLogger(); + const logger = loggingSystemMock.createLogger(); const license = licenseMock.create(); license.features$ = new BehaviorSubject({ allowAuditLogging: false, @@ -110,7 +110,7 @@ test(`does not call the underlying logger if license does not support audit logg test(`does not call the underlying logger if security audit logging is not enabled`, () => { const pluginId = 'foo'; - const logger = loggingServiceMock.createLogger(); + const logger = loggingSystemMock.createLogger(); const license = licenseMock.create(); license.features$ = new BehaviorSubject({ allowAuditLogging: true, @@ -135,7 +135,7 @@ test(`does not call the underlying logger if security audit logging is not enabl test(`calls the underlying logger after license upgrade`, () => { const pluginId = 'foo'; - const logger = loggingServiceMock.createLogger(); + const logger = loggingSystemMock.createLogger(); const license = licenseMock.create(); const features$ = new BehaviorSubject({ diff --git a/x-pack/plugins/security/server/authentication/api_keys.test.ts b/x-pack/plugins/security/server/authentication/api_keys.test.ts index 9f2a628b575d5..ad55f15545bd9 100644 --- a/x-pack/plugins/security/server/authentication/api_keys.test.ts +++ b/x-pack/plugins/security/server/authentication/api_keys.test.ts @@ -10,7 +10,7 @@ import { APIKeys } from './api_keys'; import { httpServerMock, - loggingServiceMock, + loggingSystemMock, elasticsearchServiceMock, } from '../../../../../src/core/server/mocks'; import { licenseMock } from '../../common/licensing/index.mock'; @@ -35,7 +35,7 @@ describe('API Keys', () => { apiKeys = new APIKeys({ clusterClient: mockClusterClient, - logger: loggingServiceMock.create().get('api-keys'), + logger: loggingSystemMock.create().get('api-keys'), license: mockLicense, }); }); diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 60d0521a2947e..726ffb4dbb4e9 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -14,7 +14,7 @@ import { duration, Duration } from 'moment'; import { SessionStorage } from '../../../../../src/core/server'; import { - loggingServiceMock, + loggingSystemMock, httpServiceMock, httpServerMock, elasticsearchServiceMock, @@ -48,10 +48,10 @@ function getMockOptions({ clusterClient: elasticsearchServiceMock.createClusterClient(), basePath: httpServiceMock.createSetupContract().basePath, license: licenseMock.create(), - loggers: loggingServiceMock.create(), + loggers: loggingSystemMock.create(), config: createConfig( ConfigSchema.validate({ session, authc: { selector, providers, http } }), - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), { isTLSEnabled: false } ), sessionStorageFactory: sessionStorageMock.createFactory(), diff --git a/x-pack/plugins/security/server/authentication/index.test.ts b/x-pack/plugins/security/server/authentication/index.test.ts index c7323509c00d6..0acd4fa7fae40 100644 --- a/x-pack/plugins/security/server/authentication/index.test.ts +++ b/x-pack/plugins/security/server/authentication/index.test.ts @@ -12,7 +12,7 @@ jest.mock('./authenticator'); import Boom from 'boom'; import { - loggingServiceMock, + loggingSystemMock, coreMock, httpServerMock, httpServiceMock, @@ -66,12 +66,12 @@ describe('setupAuthentication()', () => { secureCookies: true, cookieName: 'my-sid-cookie', }), - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), { isTLSEnabled: false } ), clusterClient: elasticsearchServiceMock.createClusterClient(), license: licenseMock.create(), - loggers: loggingServiceMock.create(), + loggers: loggingSystemMock.create(), getFeatureUsageService: jest .fn() .mockReturnValue(securityFeatureUsageServiceMock.createStartContract()), @@ -221,7 +221,7 @@ describe('setupAuthentication()', () => { expect(mockAuthToolkit.authenticated).not.toHaveBeenCalled(); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(loggingServiceMock.collect(mockSetupAuthenticationParams.loggers).error) + expect(loggingSystemMock.collect(mockSetupAuthenticationParams.loggers).error) .toMatchInlineSnapshot(` Array [ Array [ diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts index 1dcd2885f66dc..bab604e9e0c86 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts @@ -5,7 +5,7 @@ */ import { - loggingServiceMock, + loggingSystemMock, httpServiceMock, elasticsearchServiceMock, } from '../../../../../../src/core/server/mocks'; @@ -20,7 +20,7 @@ export function mockAuthenticationProviderOptions(options?: { name: string }) { return { client: elasticsearchServiceMock.createClusterClient(), - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), basePath, tokens: { refresh: jest.fn(), invalidate: jest.fn() }, name: options?.name ?? 'basic1', diff --git a/x-pack/plugins/security/server/authentication/tokens.test.ts b/x-pack/plugins/security/server/authentication/tokens.test.ts index 57366183050d7..b42018b93e73f 100644 --- a/x-pack/plugins/security/server/authentication/tokens.test.ts +++ b/x-pack/plugins/security/server/authentication/tokens.test.ts @@ -6,7 +6,7 @@ import { errors } from 'elasticsearch'; -import { elasticsearchServiceMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { elasticsearchServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; import { IClusterClient, ElasticsearchErrorHelpers } from '../../../../../src/core/server'; import { Tokens } from './tokens'; @@ -19,7 +19,7 @@ describe('Tokens', () => { const tokensOptions = { client: mockClusterClient, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), }; tokens = new Tokens(tokensOptions); diff --git a/x-pack/plugins/security/server/authorization/api_authorization.test.ts b/x-pack/plugins/security/server/authorization/api_authorization.test.ts index 183a36274142c..75aa27c3c88c6 100644 --- a/x-pack/plugins/security/server/authorization/api_authorization.test.ts +++ b/x-pack/plugins/security/server/authorization/api_authorization.test.ts @@ -10,7 +10,7 @@ import { coreMock, httpServerMock, httpServiceMock, - loggingServiceMock, + loggingSystemMock, } from '../../../../../src/core/server/mocks'; import { authorizationMock } from './index.mock'; @@ -18,7 +18,7 @@ describe('initAPIAuthorization', () => { test(`protected route when "mode.useRbacForRequest()" returns false continues`, async () => { const mockHTTPSetup = coreMock.createSetup().http; const mockAuthz = authorizationMock.create(); - initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingServiceMock.create().get()); + initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingSystemMock.create().get()); const [[postAuthHandler]] = mockHTTPSetup.registerOnPostAuth.mock.calls; @@ -42,7 +42,7 @@ describe('initAPIAuthorization', () => { test(`unprotected route when "mode.useRbacForRequest()" returns true continues`, async () => { const mockHTTPSetup = coreMock.createSetup().http; const mockAuthz = authorizationMock.create(); - initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingServiceMock.create().get()); + initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingSystemMock.create().get()); const [[postAuthHandler]] = mockHTTPSetup.registerOnPostAuth.mock.calls; @@ -66,7 +66,7 @@ describe('initAPIAuthorization', () => { test(`protected route when "mode.useRbacForRequest()" returns true and user is authorized continues`, async () => { const mockHTTPSetup = coreMock.createSetup().http; const mockAuthz = authorizationMock.create({ version: '1.0.0-zeta1' }); - initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingServiceMock.create().get()); + initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingSystemMock.create().get()); const [[postAuthHandler]] = mockHTTPSetup.registerOnPostAuth.mock.calls; @@ -101,7 +101,7 @@ describe('initAPIAuthorization', () => { test(`protected route when "mode.useRbacForRequest()" returns true and user isn't authorized responds with a 404`, async () => { const mockHTTPSetup = coreMock.createSetup().http; const mockAuthz = authorizationMock.create({ version: '1.0.0-zeta1' }); - initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingServiceMock.create().get()); + initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingSystemMock.create().get()); const [[postAuthHandler]] = mockHTTPSetup.registerOnPostAuth.mock.calls; diff --git a/x-pack/plugins/security/server/authorization/app_authorization.test.ts b/x-pack/plugins/security/server/authorization/app_authorization.test.ts index 1dc56161d6363..2d3a981fb3247 100644 --- a/x-pack/plugins/security/server/authorization/app_authorization.test.ts +++ b/x-pack/plugins/security/server/authorization/app_authorization.test.ts @@ -8,7 +8,7 @@ import { PluginSetupContract as FeaturesSetupContract } from '../../../features/ import { initAppAuthorization } from './app_authorization'; import { - loggingServiceMock, + loggingSystemMock, coreMock, httpServerMock, httpServiceMock, @@ -27,7 +27,7 @@ describe('initAppAuthorization', () => { initAppAuthorization( mockHTTPSetup, authorizationMock.create(), - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), createFeaturesSetupContractMock() ); @@ -49,7 +49,7 @@ describe('initAppAuthorization', () => { initAppAuthorization( mockHTTPSetup, mockAuthz, - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), createFeaturesSetupContractMock() ); @@ -74,7 +74,7 @@ describe('initAppAuthorization', () => { initAppAuthorization( mockHTTPSetup, mockAuthz, - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), createFeaturesSetupContractMock() ); @@ -100,7 +100,7 @@ describe('initAppAuthorization', () => { initAppAuthorization( mockHTTPSetup, mockAuthz, - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), createFeaturesSetupContractMock() ); @@ -140,7 +140,7 @@ describe('initAppAuthorization', () => { initAppAuthorization( mockHTTPSetup, mockAuthz, - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), createFeaturesSetupContractMock() ); diff --git a/x-pack/plugins/security/server/authorization/authorization_service.test.ts b/x-pack/plugins/security/server/authorization/authorization_service.test.ts index 978c985cfe820..4d0ab1c964741 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.test.ts +++ b/x-pack/plugins/security/server/authorization/authorization_service.test.ts @@ -25,7 +25,7 @@ import { AuthorizationService } from '.'; import { coreMock, elasticsearchServiceMock, - loggingServiceMock, + loggingSystemMock, } from '../../../../../src/core/server/mocks'; import { featuresPluginMock } from '../../../features/server/mocks'; import { licenseMock } from '../../common/licensing/index.mock'; @@ -71,7 +71,7 @@ it(`#setup returns exposed services`, () => { status: mockCoreSetup.status, clusterClient: mockClusterClient, license: mockLicense, - loggers: loggingServiceMock.create(), + loggers: loggingSystemMock.create(), kibanaIndexName, packageVersion: 'some-version', features: mockFeaturesSetup, @@ -140,7 +140,7 @@ describe('#start', () => { status: mockCoreSetup.status, clusterClient: mockClusterClient, license: mockLicense, - loggers: loggingServiceMock.create(), + loggers: loggingSystemMock.create(), kibanaIndexName, packageVersion: 'some-version', features: featuresPluginMock.createSetup(), @@ -241,7 +241,7 @@ it('#stop unsubscribes from license and ES updates.', () => { status: mockCoreSetup.status, clusterClient: mockClusterClient, license: mockLicense, - loggers: loggingServiceMock.create(), + loggers: loggingSystemMock.create(), kibanaIndexName, packageVersion: 'some-version', features: featuresPluginMock.createSetup(), diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts index 082484d5fa6b4..a1bedea9f7deb 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts @@ -7,7 +7,7 @@ import { Actions } from '.'; import { disableUICapabilitiesFactory } from './disable_ui_capabilities'; -import { httpServerMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { httpServerMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; import { authorizationMock } from './index.mock'; import { Feature } from '../../../features/server'; @@ -42,7 +42,7 @@ describe('usingPrivileges', () => { const mockAuthz = createMockAuthz({ rejectCheckPrivileges: { statusCode: 401, message: 'super informative message' }, }); - const mockLoggers = loggingServiceMock.create(); + const mockLoggers = loggingSystemMock.create(); const { usingPrivileges } = disableUICapabilitiesFactory( mockRequest, @@ -103,7 +103,7 @@ describe('usingPrivileges', () => { }, }); - expect(loggingServiceMock.collect(mockLoggers).debug).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(mockLoggers).debug).toMatchInlineSnapshot(` Array [ Array [ "Disabling all uiCapabilities because we received a 401: super informative message", @@ -116,7 +116,7 @@ describe('usingPrivileges', () => { const mockAuthz = createMockAuthz({ rejectCheckPrivileges: { statusCode: 403, message: 'even more super informative message' }, }); - const mockLoggers = loggingServiceMock.create(); + const mockLoggers = loggingSystemMock.create(); const { usingPrivileges } = disableUICapabilitiesFactory( mockRequest, @@ -176,7 +176,7 @@ describe('usingPrivileges', () => { bar: false, }, }); - expect(loggingServiceMock.collect(mockLoggers).debug).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(mockLoggers).debug).toMatchInlineSnapshot(` Array [ Array [ "Disabling all uiCapabilities because we received a 403: even more super informative message", @@ -189,7 +189,7 @@ describe('usingPrivileges', () => { const mockAuthz = createMockAuthz({ rejectCheckPrivileges: new Error('something else entirely'), }); - const mockLoggers = loggingServiceMock.create(); + const mockLoggers = loggingSystemMock.create(); const { usingPrivileges } = disableUICapabilitiesFactory( mockRequest, @@ -212,7 +212,7 @@ describe('usingPrivileges', () => { catalogue: {}, }) ).rejects.toThrowErrorMatchingSnapshot(); - expect(loggingServiceMock.collect(mockLoggers)).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(mockLoggers)).toMatchInlineSnapshot(` Object { "debug": Array [], "error": Array [], @@ -261,7 +261,7 @@ describe('usingPrivileges', () => { privileges: null, }), ], - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), mockAuthz ); @@ -347,7 +347,7 @@ describe('usingPrivileges', () => { privileges: null, }), ], - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), mockAuthz ); @@ -412,7 +412,7 @@ describe('all', () => { privileges: null, }), ], - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), mockAuthz ); diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts index e21203e60b887..8604e02b63276 100644 --- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts +++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts @@ -8,7 +8,7 @@ import { IClusterClient, Logger } from 'kibana/server'; import { RawKibanaPrivileges } from '../../common/model'; import { registerPrivilegesWithCluster } from './register_privileges_with_cluster'; -import { elasticsearchServiceMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { elasticsearchServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; const application = 'default-application'; const registerPrivilegesWithClusterTest = ( @@ -130,7 +130,7 @@ const registerPrivilegesWithClusterTest = ( } } }); - const mockLogger = loggingServiceMock.create().get() as jest.Mocked; + const mockLogger = loggingSystemMock.create().get() as jest.Mocked; let error; try { diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 0e1e2e2afeb13..4d0c9ca6c36e0 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -6,7 +6,7 @@ jest.mock('crypto', () => ({ randomBytes: jest.fn() })); -import { loggingServiceMock } from '../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../src/core/server/mocks'; import { createConfig, ConfigSchema } from './config'; describe('config schema', () => { @@ -798,13 +798,13 @@ describe('createConfig()', () => { const mockRandomBytes = jest.requireMock('crypto').randomBytes; mockRandomBytes.mockReturnValue('ab'.repeat(16)); - const logger = loggingServiceMock.create().get(); + const logger = loggingSystemMock.create().get(); const config = createConfig(ConfigSchema.validate({}, { dist: true }), logger, { isTLSEnabled: true, }); expect(config.encryptionKey).toEqual('ab'.repeat(16)); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Generating a random key for xpack.security.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.security.encryptionKey in kibana.yml", @@ -814,11 +814,11 @@ describe('createConfig()', () => { }); it('should log a warning if SSL is not configured', async () => { - const logger = loggingServiceMock.create().get(); + const logger = loggingSystemMock.create().get(); const config = createConfig(ConfigSchema.validate({}), logger, { isTLSEnabled: false }); expect(config.secureCookies).toEqual(false); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Session cookies will be transmitted over insecure connections. This is not recommended.", @@ -828,13 +828,13 @@ describe('createConfig()', () => { }); it('should log a warning if SSL is not configured yet secure cookies are being used', async () => { - const logger = loggingServiceMock.create().get(); + const logger = loggingSystemMock.create().get(); const config = createConfig(ConfigSchema.validate({ secureCookies: true }), logger, { isTLSEnabled: false, }); expect(config.secureCookies).toEqual(true); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "Using secure cookies, but SSL is not enabled inside Kibana. SSL must be configured outside of Kibana to function properly.", @@ -844,15 +844,15 @@ describe('createConfig()', () => { }); it('should set xpack.security.secureCookies if SSL is configured', async () => { - const logger = loggingServiceMock.create().get(); + const logger = loggingSystemMock.create().get(); const config = createConfig(ConfigSchema.validate({}), logger, { isTLSEnabled: true }); expect(config.secureCookies).toEqual(true); - expect(loggingServiceMock.collect(logger).warn).toEqual([]); + expect(loggingSystemMock.collect(logger).warn).toEqual([]); }); it('transforms legacy `authc.providers` into new format', () => { - const logger = loggingServiceMock.create().get(); + const logger = loggingSystemMock.create().get(); expect( createConfig( @@ -919,7 +919,7 @@ describe('createConfig()', () => { ConfigSchema.validate({ authc: { providers: ['saml', 'basic'], saml: { realm: 'saml-realm' } }, }), - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), { isTLSEnabled: true } ).authc.selector.enabled ).toBe(false); @@ -934,7 +934,7 @@ describe('createConfig()', () => { saml: { realm: 'saml-realm' }, }, }), - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), { isTLSEnabled: true } ).authc.selector.enabled ).toBe(true); @@ -954,7 +954,7 @@ describe('createConfig()', () => { }, }, }), - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), { isTLSEnabled: true } ).authc.selector.enabled ).toBe(false); @@ -971,7 +971,7 @@ describe('createConfig()', () => { }, }, }), - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), { isTLSEnabled: true } ).authc.selector.enabled ).toBe(true); @@ -989,7 +989,7 @@ describe('createConfig()', () => { }, }, }), - loggingServiceMock.create().get(), + loggingSystemMock.create().get(), { isTLSEnabled: true } ).authc.sortedProviders ).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index 1a93d6701e257..c7ff2a1e68b02 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -7,7 +7,7 @@ import { elasticsearchServiceMock, httpServiceMock, - loggingServiceMock, + loggingSystemMock, httpResourcesMock, } from '../../../../../src/core/server/mocks'; import { authenticationMock } from '../authentication/index.mock'; @@ -20,9 +20,9 @@ export const routeDefinitionParamsMock = { router: httpServiceMock.createRouter(), basePath: httpServiceMock.createBasePath(), csp: httpServiceMock.createSetupContract().csp, - logger: loggingServiceMock.create().get(), + logger: loggingSystemMock.create().get(), clusterClient: elasticsearchServiceMock.createClusterClient(), - config: createConfig(ConfigSchema.validate(config), loggingServiceMock.create().get(), { + config: createConfig(ConfigSchema.validate(config), loggingSystemMock.create().get(), { isTLSEnabled: false, }), authc: authenticationMock.create(), diff --git a/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/alerts.test.ts b/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/alerts.test.ts index fd785bca4aa24..0134f9e72ab5d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/alerts.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/alerts/handlers/alerts.test.ts @@ -7,7 +7,7 @@ import { IClusterClient, IRouter, IScopedClusterClient } from 'kibana/server'; import { elasticsearchServiceMock, httpServiceMock, - loggingServiceMock, + loggingSystemMock, } from '../../../../../../../src/core/server/mocks'; import { registerAlertRoutes } from '../routes'; import { alertingIndexGetQuerySchema } from '../../../../common/endpoint_alerts/schema/alert_index'; @@ -33,7 +33,7 @@ describe('test alerts route', () => { }); registerAlertRoutes(routerMock, { - logFactory: loggingServiceMock.create(), + logFactory: loggingSystemMock.create(), service: endpointAppContextService, config: () => Promise.resolve(createMockConfig()), }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 92835dc5329ce..ba51a3b6aa92e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -16,7 +16,7 @@ import { elasticsearchServiceMock, httpServerMock, httpServiceMock, - loggingServiceMock, + loggingSystemMock, savedObjectsClientMock, } from '../../../../../../../src/core/server/mocks'; import { @@ -63,7 +63,7 @@ describe('test endpoint route', () => { }); registerEndpointRoutes(routerMock, { - logFactory: loggingServiceMock.create(), + logFactory: loggingSystemMock.create(), service: endpointAppContextService, config: () => Promise.resolve(createMockConfig()), }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts index 9e9eaafd0f1de..f83fb5b4a5a11 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { httpServerMock, loggingServiceMock } from '../../../../../../../src/core/server/mocks'; +import { httpServerMock, loggingSystemMock } from '../../../../../../../src/core/server/mocks'; import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; @@ -18,7 +18,7 @@ describe('query builder', () => { const query = await kibanaRequestToMetadataListESQuery( mockRequest, { - logFactory: loggingServiceMock.create(), + logFactory: loggingSystemMock.create(), service: new EndpointAppContextService(), config: () => Promise.resolve(createMockConfig()), }, @@ -70,7 +70,7 @@ describe('query builder', () => { const query = await kibanaRequestToMetadataListESQuery( mockRequest, { - logFactory: loggingServiceMock.create(), + logFactory: loggingSystemMock.create(), service: new EndpointAppContextService(), config: () => Promise.resolve(createMockConfig()), }, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts index 2b94fe3576e2d..6c1f0a206ffaa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts @@ -14,7 +14,7 @@ import { import { elasticsearchServiceMock, httpServerMock, - loggingServiceMock, + loggingSystemMock, savedObjectsClientMock, } from '../../../../../../../src/core/server/mocks'; import { AgentService } from '../../../../../ingest_manager/server/services'; @@ -46,7 +46,7 @@ describe('test policy response handler', () => { it('should return the latest policy response for a host', async () => { const response = createSearchResponse(new EndpointDocGenerator().generatePolicyResponse()); const hostPolicyResponseHandler = getHostPolicyResponseHandler({ - logFactory: loggingServiceMock.create(), + logFactory: loggingSystemMock.create(), service: endpointAppContextService, config: () => Promise.resolve(createMockConfig()), }); @@ -69,7 +69,7 @@ describe('test policy response handler', () => { it('should return not found when there is no response policy for host', async () => { const hostPolicyResponseHandler = getHostPolicyResponseHandler({ - logFactory: loggingServiceMock.create(), + logFactory: loggingSystemMock.create(), service: endpointAppContextService, config: () => Promise.resolve(createMockConfig()), }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts index 47356679c8075..3eefd3e665cd6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { getResult } from '../routes/__mocks__/request_responses'; import { rulesNotificationAlertType } from './rules_notification_alert_type'; import { buildSignalsSearchQuery } from './build_signals_query'; @@ -15,12 +15,12 @@ jest.mock('./build_signals_query'); describe('rules_notification_alert_type', () => { let payload: NotificationExecutorOptions; let alert: ReturnType; - let logger: ReturnType; + let logger: ReturnType; let alertServices: AlertServicesMock; beforeEach(() => { alertServices = alertsMock.createAlertServices(); - logger = loggingServiceMock.createLogger(); + logger = loggingSystemMock.createLogger(); payload = { alertId: '1111', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.test.ts index 0c9ccf069b3b6..03d08625000ec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { getNotificationResult, getResult } from '../routes/__mocks__/request_responses'; import { isAlertTypes, isNotificationAlertExecutor } from './types'; import { rulesNotificationAlertType } from './rules_notification_alert_type'; @@ -21,7 +21,7 @@ describe('types', () => { it('isNotificationAlertExecutor should return true it passed object is NotificationAlertTypeDefinition type', () => { expect( isNotificationAlertExecutor( - rulesNotificationAlertType({ logger: loggingServiceMock.createLogger() }) + rulesNotificationAlertType({ logger: loggingSystemMock.createLogger() }) ) ).toEqual(true); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 01ee41e3b877c..101c998efa242 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -10,7 +10,7 @@ import { SavedObject, SavedObjectsFindResponse, } from '../../../../../../../../src/core/server'; -import { loggingServiceMock } from '../../../../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks'; import { RuleTypeParams } from '../../types'; import { IRuleStatusAttributes } from '../../rules/types'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; @@ -394,7 +394,7 @@ export const exampleFindRuleStatusResponse: ( saved_objects: mockStatuses.map((obj) => ({ ...obj, score: 1 })), }); -export const mockLogger: Logger = loggingServiceMock.createLogger(); +export const mockLogger: Logger = loggingSystemMock.createLogger(); export const sampleBulkErrorItem = ( { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index a2dc33ba1c2bf..23c2d6068c09c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; import { signalRulesAlertType } from './signal_rule_alert_type'; import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; @@ -69,13 +69,13 @@ describe('rules_notification_alert_type', () => { }; let payload: jest.Mocked; let alert: ReturnType; - let logger: ReturnType; + let logger: ReturnType; let alertServices: AlertServicesMock; let ruleStatusService: Record; beforeEach(() => { alertServices = alertsMock.createAlertServices(); - logger = loggingServiceMock.createLogger(); + logger = loggingSystemMock.createLogger(); ruleStatusService = { success: jest.fn(), find: jest.fn(), diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts index b4489e5700159..1e01e04332f43 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts @@ -8,7 +8,7 @@ import { Feature } from '../../../../plugins/features/server'; import { Space } from '../../common/model/space'; import { setupCapabilitiesSwitcher } from './capabilities_switcher'; import { Capabilities, CoreSetup } from 'src/core/server'; -import { coreMock, httpServerMock, loggingServiceMock } from 'src/core/server/mocks'; +import { coreMock, httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; import { featuresPluginMock } from '../../../features/server/mocks'; import { spacesServiceMock } from '../spaces_service/spaces_service.mock'; import { PluginsStart } from '../plugin'; @@ -109,7 +109,7 @@ const setup = (space: Space) => { const spacesService = spacesServiceMock.createSetupContract(); spacesService.getActiveSpace.mockResolvedValue(space); - const logger = loggingServiceMock.createLogger(); + const logger = loggingSystemMock.createLogger(); const switcher = setupCapabilitiesSwitcher( (coreSetup as unknown) as CoreSetup, diff --git a/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts b/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts index 80cc7428e28e7..f281cd8efaa9f 100644 --- a/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts +++ b/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts @@ -6,7 +6,7 @@ import { createDefaultSpace } from './create_default_space'; import { SavedObjectsErrorHelpers } from 'src/core/server'; -import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { loggingSystemMock } from '../../../../../src/core/server/mocks'; interface MockServerSettings { defaultExists?: boolean; @@ -57,7 +57,7 @@ const createMockDeps = (settings: MockServerSettings = {}) => { }; }), }), - logger: loggingServiceMock.createLogger(), + logger: loggingSystemMock.createLogger(), }; }; diff --git a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts index 2d677565164a2..311bedd0bf9e7 100644 --- a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts +++ b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts @@ -16,7 +16,7 @@ import { SavedObjectsRepository, SavedObjectsErrorHelpers, } from '../../../../../src/core/server'; -import { coreMock, loggingServiceMock } from 'src/core/server/mocks'; +import { coreMock, loggingSystemMock } from 'src/core/server/mocks'; import { licensingMock } from '../../../licensing/server/mocks'; import { SpacesLicenseService } from '../../common/licensing'; import { ILicense } from '../../../licensing/server'; @@ -59,7 +59,7 @@ const setup = ({ elasticsearchStatus, savedObjectsStatus, license }: SetupOpts) const license$ = new Rx.BehaviorSubject(license); - const logger = loggingServiceMock.createLogger(); + const logger = loggingSystemMock.createLogger(); const { license: spacesLicense } = new SpacesLicenseService().setup({ license$ }); diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index e596eade802fd..17a1fbcca73bd 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -17,7 +17,7 @@ import { } from '../../../../../../src/core/server'; import { elasticsearchServiceMock, - loggingServiceMock, + loggingSystemMock, coreMock, } from '../../../../../../src/core/server/mocks'; import * as kbnTestServer from '../../../../../../src/test_utils/kbn_server'; @@ -121,7 +121,7 @@ describe.skip('onPostAuthInterceptor', () => { // Mock esNodesCompatibility$ to prevent `root.start()` from blocking on ES version check elasticsearch.esNodesCompatibility$ = elasticsearchServiceMock.createInternalSetup().esNodesCompatibility$; - const loggingMock = loggingServiceMock.create().asLoggerFactory().get('xpack', 'spaces'); + const loggingMock = loggingSystemMock.create().asLoggerFactory().get('xpack', 'spaces'); const featuresPlugin = { getFeatures: () => diff --git a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts index 0abf545fa7493..8ec2e6f978d81 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts @@ -9,12 +9,12 @@ import { DEFAULT_SPACE_ID } from '../../common/constants'; import { createSpacesTutorialContextFactory } from './spaces_tutorial_context_factory'; import { SpacesService } from '../spaces_service'; import { SpacesAuditLogger } from './audit_logger'; -import { coreMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { coreMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; import { spacesServiceMock } from '../spaces_service/spaces_service.mock'; import { spacesConfig } from './__fixtures__'; import { securityMock } from '../../../security/server/mocks'; -const log = loggingServiceMock.createLogger(); +const log = loggingSystemMock.createLogger(); const service = new SpacesService(log); diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index 53f5a219dda5b..b604554cbc59a 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -16,7 +16,7 @@ import { } from '../__fixtures__'; import { CoreSetup, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server'; import { - loggingServiceMock, + loggingSystemMock, httpServiceMock, httpServerMock, coreMock, @@ -68,7 +68,7 @@ describe('copy to space', () => { createResolveSavedObjectsImportErrorsMock() ); - const log = loggingServiceMock.create().get('spaces'); + const log = loggingSystemMock.create().get('spaces'); const coreStart = coreMock.createStart(); coreStart.savedObjects = createMockSavedObjectsService(spaces); diff --git a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts index f31ef657642e7..5461aaf1e36ea 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts @@ -18,7 +18,7 @@ import { SavedObjectsErrorHelpers, } from 'src/core/server'; import { - loggingServiceMock, + loggingSystemMock, httpServiceMock, httpServerMock, coreMock, @@ -40,7 +40,7 @@ describe('Spaces Public API', () => { const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects); - const log = loggingServiceMock.create().get('spaces'); + const log = loggingSystemMock.create().get('spaces'); const coreStart = coreMock.createStart(); diff --git a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts index 55e153cf47f5b..ac9a46ee9c3fa 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts @@ -13,7 +13,7 @@ import { import { initGetSpaceApi } from './get'; import { CoreSetup, kibanaResponseFactory } from 'src/core/server'; import { - loggingServiceMock, + loggingSystemMock, httpServiceMock, httpServerMock, coreMock, @@ -36,7 +36,7 @@ describe('GET space', () => { const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects); - const log = loggingServiceMock.create().get('spaces'); + const log = loggingSystemMock.create().get('spaces'); const service = new SpacesService(log); const spacesService = await service.setup({ diff --git a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts index aabd4900c5469..ec841808f771d 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts @@ -12,7 +12,7 @@ import { } from '../__fixtures__'; import { CoreSetup, kibanaResponseFactory } from 'src/core/server'; import { - loggingServiceMock, + loggingSystemMock, httpServiceMock, httpServerMock, coreMock, @@ -36,7 +36,7 @@ describe('GET /spaces/space', () => { const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects); - const log = loggingServiceMock.create().get('spaces'); + const log = loggingSystemMock.create().get('spaces'); const service = new SpacesService(log); const spacesService = await service.setup({ diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts index 5e09308f07d31..6aa89b36b020a 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts @@ -12,7 +12,7 @@ import { } from '../__fixtures__'; import { CoreSetup, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server'; import { - loggingServiceMock, + loggingSystemMock, httpServerMock, httpServiceMock, coreMock, @@ -36,7 +36,7 @@ describe('Spaces Public API', () => { const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects); - const log = loggingServiceMock.create().get('spaces'); + const log = loggingSystemMock.create().get('spaces'); const service = new SpacesService(log); const spacesService = await service.setup({ diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts index 7b068d3784043..ebdffa20a6c8e 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts @@ -13,7 +13,7 @@ import { } from '../__fixtures__'; import { CoreSetup, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server'; import { - loggingServiceMock, + loggingSystemMock, httpServiceMock, httpServerMock, coreMock, @@ -37,7 +37,7 @@ describe('PUT /api/spaces/space', () => { const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects); - const log = loggingServiceMock.create().get('spaces'); + const log = loggingSystemMock.create().get('spaces'); const service = new SpacesService(log); const spacesService = await service.setup({ diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts index 3e1a849a9bdfa..b341d76c86649 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts @@ -5,7 +5,7 @@ */ import * as Rx from 'rxjs'; import { SpacesService } from './spaces_service'; -import { coreMock, httpServerMock, loggingServiceMock } from 'src/core/server/mocks'; +import { coreMock, httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; import { SpacesAuditLogger } from '../lib/audit_logger'; import { KibanaRequest, @@ -18,7 +18,7 @@ import { getSpaceIdFromPath } from '../../common/lib/spaces_url_parser'; import { spacesConfig } from '../lib/__fixtures__'; import { securityMock } from '../../../security/server/mocks'; -const mockLogger = loggingServiceMock.createLogger(); +const mockLogger = loggingSystemMock.createLogger(); const createService = async (serverBasePath: string = '') => { const spacesService = new SpacesService(mockLogger); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts index 9c2593ee79f93..dea9974791a88 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts @@ -6,7 +6,7 @@ jest.mock('../es_indices_state_check', () => ({ esIndicesStateCheck: jest.fn() })); import { BehaviorSubject } from 'rxjs'; import { Logger } from 'src/core/server'; -import { loggingServiceMock } from 'src/core/server/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { IndexGroup, @@ -60,7 +60,7 @@ describe('reindexService', () => { runWhileIndexGroupLocked: jest.fn(async (group: string, f: any) => f({ attributes: {} })), }; callCluster = jest.fn(); - log = loggingServiceMock.create().get(); + log = loggingSystemMock.create().get(); licensingPluginSetup = licensingMock.createSetup(); licensingPluginSetup.license$ = new BehaviorSubject( licensingMock.createLicense({ From c87b00dc941f982ddcef574dce88db7efb0a11c6 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Tue, 23 Jun 2020 15:33:43 -0600 Subject: [PATCH 22/36] [Maps] Remove extra layer of telemetry nesting under "attributes" (#66137) * Return attributes when telemetry created instead of whole saved object. Update integration test * Change 'maps-telemetry' to 'maps' * No need to create a saved object anymore. This is leftover from task manager telemetry mgmt * Add test confirming attrs undefined. Change tests to check for 'maps' iso 'maps-telemetry' * Add two more tests confirming expected telemetry shape * Review feedback. Use TELEMETRY_TYPE constant and set to APP_ID --- x-pack/plugins/maps/common/constants.ts | 2 +- .../maps/server/maps_telemetry/maps_telemetry.ts | 13 ++----------- .../apis/telemetry/telemetry_local.js | 7 ++++--- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index be3de22fa011e..1d795c370dc00 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -25,7 +25,7 @@ export const EMS_TILES_VECTOR_TILE_PATH = 'vector/tile'; export const MAP_SAVED_OBJECT_TYPE = 'map'; export const APP_ID = 'maps'; export const APP_ICON = 'gisApp'; -export const TELEMETRY_TYPE = 'maps-telemetry'; +export const TELEMETRY_TYPE = APP_ID; export const MAP_APP_PATH = `app/${APP_ID}`; export const GIS_API_PATH = `api/${APP_ID}`; diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts index 463d3f3b3939d..0e29eca244642 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -11,12 +11,7 @@ import { SavedObjectAttribute, } from 'kibana/server'; import { IFieldType, IIndexPattern } from 'src/plugins/data/public'; -import { - SOURCE_TYPES, - ES_GEO_FIELD_TYPE, - MAP_SAVED_OBJECT_TYPE, - TELEMETRY_TYPE, -} from '../../common/constants'; +import { SOURCE_TYPES, ES_GEO_FIELD_TYPE, MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; import { LayerDescriptor } from '../../common/descriptor_types'; import { MapSavedObject } from '../../common/map_saved_object_type'; // @ts-ignore @@ -186,9 +181,5 @@ export async function getMapsTelemetry(config: MapsConfigType) { const settings: SavedObjectAttribute = { showMapVisualizationTypes: config.showMapVisualizationTypes, }; - const mapsTelemetry = buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, settings }); - return await savedObjectsClient.create(TELEMETRY_TYPE, mapsTelemetry, { - id: TELEMETRY_TYPE, - overwrite: true, - }); + return buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, settings }); } diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js index f06af3baa2301..9dbc3e1c8a5bb 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js @@ -76,9 +76,10 @@ export default function ({ getService }) { expect(stats.stack_stats.kibana.plugins.apm.services_per_agent).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.infraops.last_24_hours).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.kql.defaultQueryLanguage).to.be.a('string'); - expect(stats.stack_stats.kibana.plugins['maps-telemetry'].attributes.timeCaptured).to.be.a( - 'string' - ); + expect(stats.stack_stats.kibana.plugins.maps.timeCaptured).to.be.a('string'); + expect(stats.stack_stats.kibana.plugins.maps.attributes).to.be(undefined); + expect(stats.stack_stats.kibana.plugins.maps.id).to.be(undefined); + expect(stats.stack_stats.kibana.plugins.maps.type).to.be(undefined); expect(stats.stack_stats.kibana.plugins.reporting.enabled).to.be(true); expect(stats.stack_stats.kibana.plugins.rollups.index_patterns).to.be.an('object'); From 3e113151ad1874b514463b1b9a97dd7182ee2525 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 23 Jun 2020 23:42:09 +0200 Subject: [PATCH 23/36] [Index Management] Fix API Integration Test and use of `timestamp_field` (#69666) * fix types and functional api integration test * access timestamp field name in object * temporarily skip the API integration test and fix ts issue --- .../home/data_streams_tab.helpers.ts | 2 +- .../index_management/common/types/data_streams.ts | 13 +++++++++++-- .../data_stream_table/data_stream_table.tsx | 2 +- .../management/index_management/data_streams.ts | 9 +++++---- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index ef6aca44a1754..572889954db6a 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -90,7 +90,7 @@ export const setup = async (): Promise => { export const createDataStreamPayload = (name: string): DataStream => ({ name, - timeStampField: '@timestamp', + timeStampField: { name: '@timestamp', mapping: { type: 'date' } }, indices: [ { name: 'indexName', diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index 5b743296d868b..772ed43459bcf 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -4,9 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +interface TimestampFieldFromEs { + name: string; + mapping: { + type: string; + }; +} + +type TimestampField = TimestampFieldFromEs; + export interface DataStreamFromEs { name: string; - timestamp_field: string; + timestamp_field: TimestampFieldFromEs; indices: DataStreamIndexFromEs[]; generation: number; } @@ -18,7 +27,7 @@ export interface DataStreamIndexFromEs { export interface DataStream { name: string; - timeStampField: string; + timeStampField: TimestampField; indices: DataStreamIndex[]; generation: number; } diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index 54b215e561b46..54035e2193624 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -59,7 +59,7 @@ export const DataStreamTable: React.FunctionComponent = ({ ), }, { - field: 'timeStampField', + field: 'timeStampField.name', name: i18n.translate('xpack.idxMgmt.dataStreamList.table.timeStampFieldColumnTitle', { defaultMessage: 'Timestamp field', }), diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index 219f2471f8e68..e1756df42ca25 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -50,17 +50,18 @@ export default function ({ getService }: FtrProviderContext) { const deleteDataStream = (name: string) => { return es.dataManagement - .deleteComposableIndexTemplate({ + .deleteDataStream({ name, }) .then(() => - es.dataManagement.deleteDataStream({ + es.dataManagement.deleteComposableIndexTemplate({ name, }) ); }; - describe('Data streams', function () { + // Unskip once ES snapshot has been promoted that updates the data stream response + describe.skip('Data streams', function () { const testDataStreamName = 'test-data-stream'; describe('Get', () => { @@ -79,7 +80,7 @@ export default function ({ getService }: FtrProviderContext) { expect(dataStreams).to.eql([ { name: testDataStreamName, - timeStampField: '@timestamp', + timeStampField: { name: '@timestamp', mapping: { type: 'date' } }, indices: [ { name: indexName, From 29fbdd56d9209b14860dbf812f930e7e78dc7aec Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 23 Jun 2020 17:47:59 -0400 Subject: [PATCH 24/36] [SECURITY] Add endpoint alerts url (#69707) * Add back endpoint alerts url * hack to move on * fix type * fix test --- .../security_solution/common/constants.ts | 3 ++ .../public/app/home/home_navigations.tsx | 8 +++++ .../security_solution/public/app/types.ts | 1 + .../components/navigation/index.test.tsx | 19 ++++++++++-- .../common/components/navigation/types.ts | 3 +- .../public/endpoint_alerts/routes.tsx | 2 +- .../public/endpoint_alerts/store/selectors.ts | 5 ++- .../security_solution/public/plugin.tsx | 31 +++++++++++++++++++ 8 files changed, 66 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 0d162c068376f..58431e405ea8b 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -42,6 +42,9 @@ export const APP_TIMELINES_PATH = `${APP_PATH}/timelines`; export const APP_CASES_PATH = `${APP_PATH}/cases`; export const APP_MANAGEMENT_PATH = `${APP_PATH}/management`; +export const SHOW_ENDPOINT_ALERTS_NAV = true; +export const APP_ENDPOINT_ALERTS_PATH = `${APP_PATH}/endpoint-alerts`; + /** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */ export const DEFAULT_INDEX_PATTERN = [ 'apm-*-transaction*', diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx index 88e9d4179a971..8839919af2060 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx @@ -15,6 +15,7 @@ import { APP_TIMELINES_PATH, APP_CASES_PATH, APP_MANAGEMENT_PATH, + APP_ENDPOINT_ALERTS_PATH, } from '../../../common/constants'; export const navTabs: SiemNavTab = { @@ -68,4 +69,11 @@ export const navTabs: SiemNavTab = { disabled: false, urlKey: SecurityPageName.management, }, + [SecurityPageName.endpointAlerts]: { + id: SecurityPageName.endpointAlerts, + name: 'Endpoint Alerts', // No Need of i18n since, it is just temporary + href: APP_ENDPOINT_ALERTS_PATH, + disabled: false, + urlKey: SecurityPageName.management, // Just to make type happy, this should go away soon + }, }; diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 4bd888e87bbdc..866a19b15771e 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -27,6 +27,7 @@ export enum SecurityPageName { timelines = 'timelines', case = 'case', management = 'management', + endpointAlerts = 'endpointAlerts', } export interface SecuritySubPluginStore { initialState: Record; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx index a99497090f843..cab4ef8ead63f 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx @@ -140,6 +140,13 @@ describe('SIEM Navigation', () => { name: 'Timelines', urlKey: 'timeline', }, + endpointAlerts: { + disabled: false, + href: '/app/security/endpoint-alerts', + id: 'endpointAlerts', + name: 'Endpoint Alerts', + urlKey: 'management', + }, }, pageName: 'hosts', pathName: '/', @@ -185,7 +192,7 @@ describe('SIEM Navigation', () => { wrapper.setProps({ pageName: 'network', pathName: '/', - tabName: undefined, + tabName: 'authentications', }); wrapper.update(); expect(setBreadcrumbs).toHaveBeenNthCalledWith( @@ -209,7 +216,13 @@ describe('SIEM Navigation', () => { name: 'Cases', urlKey: 'case', }, - + endpointAlerts: { + disabled: false, + href: '/app/security/endpoint-alerts', + id: 'endpointAlerts', + name: 'Endpoint Alerts', + urlKey: 'management', + }, hosts: { disabled: false, href: '/app/security/hosts', @@ -252,7 +265,7 @@ describe('SIEM Navigation', () => { savedQuery: undefined, search: '', state: undefined, - tabName: undefined, + tabName: 'authentications', timeline: { id: '', isOpen: false }, timerange: { global: { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index 80302be18355c..a870c790527b7 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -48,7 +48,8 @@ export type SiemNavTabKey = | SecurityPageName.alerts | SecurityPageName.timelines | SecurityPageName.case - | SecurityPageName.management; + | SecurityPageName.management + | SecurityPageName.endpointAlerts; export type SiemNavTab = Record; diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/routes.tsx b/x-pack/plugins/security_solution/public/endpoint_alerts/routes.tsx index acc6a82e29a2c..1c92919aa982f 100644 --- a/x-pack/plugins/security_solution/public/endpoint_alerts/routes.tsx +++ b/x-pack/plugins/security_solution/public/endpoint_alerts/routes.tsx @@ -11,7 +11,7 @@ import { AlertIndex } from './view'; export const EndpointAlertsRoutes: React.FC = () => ( - + diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/store/selectors.ts b/x-pack/plugins/security_solution/public/endpoint_alerts/store/selectors.ts index ab0e4165a2577..878c5f4fd2bb8 100644 --- a/x-pack/plugins/security_solution/public/endpoint_alerts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/endpoint_alerts/store/selectors.ts @@ -44,7 +44,10 @@ export const alertListPagination = createStructuredSelector({ * Returns a boolean based on whether or not the user is on the alerts page */ export const isOnAlertPage = (state: Immutable): boolean => { - return state.location ? state.location.pathname === '/endpoint-alerts' : false; + return state.location + ? state.location.pathname === '/endpoint-alerts' || + window.location.pathname.includes('/endpoint-alerts') + : false; }; /** diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 58f0a0ddb749e..360c81abadc81 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -33,6 +33,8 @@ import { APP_TIMELINES_PATH, APP_MANAGEMENT_PATH, APP_CASES_PATH, + SHOW_ENDPOINT_ALERTS_NAV, + APP_ENDPOINT_ALERTS_PATH, } from '../common/constants'; import { ConfigureEndpointDatasource } from './management/pages/policy/view/ingest_manager_integration/configure_datasource'; @@ -290,6 +292,35 @@ export class Plugin implements IPlugin { + const [ + { coreStart, startPlugins, store, services }, + { renderApp, composeLibs }, + { endpointAlertsSubPlugin }, + ] = await Promise.all([ + mountSecurityFactory(), + this.downloadAssets(), + this.downloadSubPlugins(), + ]); + return renderApp({ + ...composeLibs(coreStart), + ...params, + services, + store, + SubPluginRoutes: endpointAlertsSubPlugin.start(coreStart, startPlugins).SubPluginRoutes, + }); + }, + }); + } + core.application.register({ id: 'siem', appRoute: 'app/siem', From e3d01bf450bd42c359ad8ff5607ec1bcf1faa05a Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 23 Jun 2020 17:56:58 -0400 Subject: [PATCH 25/36] remove scroll in drag & drop context (#69710) --- .../drag_and_drop/drag_drop_context_wrapper.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index 32f05e7c837a7..3edc1d0d84b69 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -168,18 +168,6 @@ export const DragDropContextWrapper = connector(DragDropContextWrapperComponent) DragDropContextWrapper.displayName = 'DragDropContextWrapper'; const onBeforeCapture = (before: BeforeCapture) => { - const x = - window.pageXOffset !== undefined - ? window.pageXOffset - : (document.documentElement || document.body.parentNode || document.body).scrollLeft; - - const y = - window.pageYOffset !== undefined - ? window.pageYOffset - : (document.documentElement || document.body.parentNode || document.body).scrollTop; - - window.onscroll = () => window.scrollTo(x, y); - if (!draggableIsField(before)) { document.body.classList.add(IS_DRAGGING_CLASS_NAME); } From 6a016d0b57b80748a41b04d23d5e2a90afee5906 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 23 Jun 2020 18:34:17 -0500 Subject: [PATCH 26/36] [Metrics UI] Add inventory alert preview (#68909) Co-authored-by: Elastic Machine --- .../infra/common/alerting/metrics/types.ts | 20 ++- .../alerting/common/get_alert_preview.ts | 51 ++++++ .../infra/public/alerting/common/index.ts | 55 ++++++ .../inventory/components}/alert_dropdown.tsx | 0 .../inventory/components}/alert_flyout.tsx | 0 .../inventory/components}/expression.tsx | 163 +++++++++++++++++- .../inventory/components}/metric.tsx | 0 .../inventory/components}/node_type.tsx | 0 .../inventory/components}/validation.tsx | 0 .../inventory/index.ts} | 11 +- .../components/expression.tsx | 78 ++------- .../infra/public/pages/metrics/index.tsx | 2 +- .../components/waffle/node_context_menu.tsx | 2 +- x-pack/plugins/infra/public/plugin.ts | 4 +- .../evaluate_condition.ts | 136 +++++++++++++++ .../inventory_metric_threshold_executor.ts | 125 +------------- ...review_inventory_metric_threshold_alert.ts | 83 +++++++++ .../inventory_metric_threshold/types.ts | 5 +- .../preview_metric_threshold_alert.ts | 3 +- .../infra/server/routes/alerting/preview.ts | 33 +++- 20 files changed, 563 insertions(+), 208 deletions(-) create mode 100644 x-pack/plugins/infra/public/alerting/common/get_alert_preview.ts create mode 100644 x-pack/plugins/infra/public/alerting/common/index.ts rename x-pack/plugins/infra/public/{components/alerting/inventory => alerting/inventory/components}/alert_dropdown.tsx (100%) rename x-pack/plugins/infra/public/{components/alerting/inventory => alerting/inventory/components}/alert_flyout.tsx (100%) rename x-pack/plugins/infra/public/{components/alerting/inventory => alerting/inventory/components}/expression.tsx (73%) rename x-pack/plugins/infra/public/{components/alerting/inventory => alerting/inventory/components}/metric.tsx (100%) rename x-pack/plugins/infra/public/{components/alerting/inventory => alerting/inventory/components}/node_type.tsx (100%) rename x-pack/plugins/infra/public/{components/alerting/inventory => alerting/inventory/components}/validation.tsx (100%) rename x-pack/plugins/infra/public/{components/alerting/inventory/metric_inventory_threshold_alert_type.ts => alerting/inventory/index.ts} (73%) create mode 100644 x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts create mode 100644 x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert.ts diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index a6184080cb774..0c1e5090def91 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -5,6 +5,7 @@ */ import * as rt from 'io-ts'; +import { ItemTypeRT } from '../../inventory_models/types'; // TODO: Have threshold and inventory alerts import these types from this file instead of from their // local directories @@ -39,7 +40,16 @@ const baseAlertRequestParamsRT = rt.intersection([ sourceId: rt.string, }), rt.type({ - lookback: rt.union([rt.literal('h'), rt.literal('d'), rt.literal('w'), rt.literal('M')]), + lookback: rt.union([ + rt.literal('ms'), + rt.literal('s'), + rt.literal('m'), + rt.literal('h'), + rt.literal('d'), + rt.literal('w'), + rt.literal('M'), + rt.literal('y'), + ]), criteria: rt.array(rt.any), alertInterval: rt.string, }), @@ -61,10 +71,13 @@ export type MetricThresholdAlertPreviewRequestParams = rt.TypeOf< const inventoryAlertPreviewRequestParamsRT = rt.intersection([ baseAlertRequestParamsRT, rt.type({ - nodeType: rt.string, + nodeType: ItemTypeRT, alertType: rt.literal(METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID), }), ]); +export type InventoryAlertPreviewRequestParams = rt.TypeOf< + typeof inventoryAlertPreviewRequestParamsRT +>; export const alertPreviewRequestParamsRT = rt.union([ metricThresholdAlertPreviewRequestParamsRT, @@ -80,3 +93,6 @@ export const alertPreviewSuccessResponsePayloadRT = rt.type({ tooManyBuckets: rt.number, }), }); +export type AlertPreviewSuccessResponsePayload = rt.TypeOf< + typeof alertPreviewSuccessResponsePayloadRT +>; diff --git a/x-pack/plugins/infra/public/alerting/common/get_alert_preview.ts b/x-pack/plugins/infra/public/alerting/common/get_alert_preview.ts new file mode 100644 index 0000000000000..0db1cd57e093f --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/common/get_alert_preview.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { HttpSetup } from 'src/core/public'; +import { + INFRA_ALERT_PREVIEW_PATH, + METRIC_THRESHOLD_ALERT_TYPE_ID, + METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, + alertPreviewRequestParamsRT, + alertPreviewSuccessResponsePayloadRT, +} from '../../../common/alerting/metrics'; + +async function getAlertPreview({ + fetch, + params, + alertType, +}: { + fetch: HttpSetup['fetch']; + params: rt.TypeOf; + alertType: + | typeof METRIC_THRESHOLD_ALERT_TYPE_ID + | typeof METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID; +}): Promise> { + return await fetch(`${INFRA_ALERT_PREVIEW_PATH}`, { + method: 'POST', + body: JSON.stringify({ + ...params, + alertType, + }), + }); +} + +export const getMetricThresholdAlertPreview = ({ + fetch, + params, +}: { + fetch: HttpSetup['fetch']; + params: rt.TypeOf; +}) => getAlertPreview({ fetch, params, alertType: METRIC_THRESHOLD_ALERT_TYPE_ID }); + +export const getInventoryAlertPreview = ({ + fetch, + params, +}: { + fetch: HttpSetup['fetch']; + params: rt.TypeOf; +}) => getAlertPreview({ fetch, params, alertType: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID }); diff --git a/x-pack/plugins/infra/public/alerting/common/index.ts b/x-pack/plugins/infra/public/alerting/common/index.ts new file mode 100644 index 0000000000000..33f9c856e7166 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/common/index.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export * from './get_alert_preview'; + +export const previewOptions = [ + { + value: 'h', + text: i18n.translate('xpack.infra.metrics.alertFlyout.lastHourLabel', { + defaultMessage: 'Last hour', + }), + shortText: i18n.translate('xpack.infra.metrics.alertFlyout.hourLabel', { + defaultMessage: 'hour', + }), + }, + { + value: 'd', + text: i18n.translate('xpack.infra.metrics.alertFlyout.lastDayLabel', { + defaultMessage: 'Last day', + }), + shortText: i18n.translate('xpack.infra.metrics.alertFlyout.dayLabel', { + defaultMessage: 'day', + }), + }, + { + value: 'w', + text: i18n.translate('xpack.infra.metrics.alertFlyout.lastWeekLabel', { + defaultMessage: 'Last week', + }), + shortText: i18n.translate('xpack.infra.metrics.alertFlyout.weekLabel', { + defaultMessage: 'week', + }), + }, + { + value: 'M', + text: i18n.translate('xpack.infra.metrics.alertFlyout.lastMonthLabel', { + defaultMessage: 'Last month', + }), + shortText: i18n.translate('xpack.infra.metrics.alertFlyout.monthLabel', { + defaultMessage: 'month', + }), + }, +]; + +export const firedTimeLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTime', { + defaultMessage: 'time', +}); +export const firedTimesLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTimes', { + defaultMessage: 'times', +}); diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/alert_dropdown.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx rename to x-pack/plugins/infra/public/alerting/inventory/components/alert_dropdown.tsx diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_flyout.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/alerting/inventory/alert_flyout.tsx rename to x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx similarity index 73% rename from x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx rename to x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index ce14897991e60..ef73d6ff96e41 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { debounce } from 'lodash'; +import { debounce, pick } from 'lodash'; +import { Unit } from '@elastic/datemath'; import React, { useCallback, useMemo, useEffect, useState, ChangeEvent } from 'react'; import { EuiFlexGroup, @@ -15,9 +16,20 @@ import { EuiFormRow, EuiButtonEmpty, EuiFieldSearch, + EuiSelect, + EuiButton, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { + previewOptions, + firedTimeLabel, + firedTimesLabel, + getInventoryAlertPreview as getAlertPreview, +} from '../../../alerting/common'; +import { AlertPreviewSuccessResponsePayload } from '../../../../common/alerting/metrics/types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIntervalInSeconds } from '../../../../server/utils/get_interval_in_seconds'; import { Comparator, // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -52,6 +64,8 @@ import { NodeTypeExpression } from './node_type'; import { InfraWaffleMapOptions } from '../../../lib/lib'; import { convertKueryToElasticSearchQuery } from '../../../utils/kuery'; +import { validateMetricThreshold } from './validation'; + const FILTER_TYPING_DEBOUNCE_MS = 500; interface AlertContextMeta { @@ -65,18 +79,16 @@ interface Props { alertParams: { criteria: InventoryMetricConditions[]; nodeType: InventoryItemType; - groupBy?: string; filterQuery?: string; filterQueryText?: string; sourceId?: string; }; + alertInterval: string; alertsContext: AlertsContextValue; setAlertParams(key: string, value: any): void; setAlertProperty(key: string, value: any): void; } -type TimeUnit = 's' | 'm' | 'h' | 'd'; - const defaultExpression = { metric: 'cpu' as SnapshotMetricType, comparator: Comparator.GT, @@ -86,7 +98,7 @@ const defaultExpression = { } as InventoryMetricConditions; export const Expressions: React.FC = (props) => { - const { setAlertParams, alertParams, errors, alertsContext } = props; + const { setAlertParams, alertParams, errors, alertsContext, alertInterval } = props; const { source, createDerivedIndexPattern } = useSourceViaHttp({ sourceId: 'default', type: 'metrics', @@ -94,7 +106,32 @@ export const Expressions: React.FC = (props) => { toastWarning: alertsContext.toastNotifications.addWarning, }); const [timeSize, setTimeSize] = useState(1); - const [timeUnit, setTimeUnit] = useState('m'); + const [timeUnit, setTimeUnit] = useState('m'); + + const [previewLookbackInterval, setPreviewLookbackInterval] = useState('h'); + const [isPreviewLoading, setIsPreviewLoading] = useState(false); + const [previewError, setPreviewError] = useState(false); + const [previewResult, setPreviewResult] = useState( + null + ); + + const previewIntervalError = useMemo(() => { + const intervalInSeconds = getIntervalInSeconds(alertInterval); + const lookbackInSeconds = getIntervalInSeconds(`1${previewLookbackInterval}`); + if (intervalInSeconds >= lookbackInSeconds) { + return true; + } + return false; + }, [previewLookbackInterval, alertInterval]); + + const isPreviewDisabled = useMemo(() => { + if (previewIntervalError) return true; + const validationResult = validateMetricThreshold({ criteria: alertParams.criteria } as any); + const hasValidationErrors = Object.values(validationResult.errors).some((result) => + Object.values(result).some((arr) => Array.isArray(arr) && arr.length) + ); + return hasValidationErrors; + }, [alertParams.criteria, previewIntervalError]); const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ createDerivedIndexPattern, @@ -173,7 +210,7 @@ export const Expressions: React.FC = (props) => { ...c, timeUnit: tu, })); - setTimeUnit(tu as TimeUnit); + setTimeUnit(tu as Unit); setAlertParams('criteria', criteria); }, [alertParams.criteria, setAlertParams] @@ -216,6 +253,33 @@ export const Expressions: React.FC = (props) => { } }, [alertsContext.metadata, derivedIndexPattern, setAlertParams]); + const onSelectPreviewLookbackInterval = useCallback((e) => { + setPreviewLookbackInterval(e.target.value); + setPreviewResult(null); + }, []); + + const onClickPreview = useCallback(async () => { + setIsPreviewLoading(true); + setPreviewResult(null); + setPreviewError(false); + try { + const result = await getAlertPreview({ + fetch: alertsContext.http.fetch, + params: { + ...pick(alertParams, 'criteria', 'nodeType'), + sourceId: alertParams.sourceId, + lookback: previewLookbackInterval as Unit, + alertInterval, + }, + }); + setPreviewResult(result); + } catch (e) { + setPreviewError(true); + } finally { + setIsPreviewLoading(false); + } + }, [alertParams, alertInterval, alertsContext, previewLookbackInterval]); + useEffect(() => { const md = alertsContext.metadata; if (!alertParams.nodeType) { @@ -332,6 +396,91 @@ export const Expressions: React.FC = (props) => { + + <> + + + + + + + {i18n.translate('xpack.infra.metrics.alertFlyout.testAlertTrigger', { + defaultMessage: 'Test alert trigger', + })} + + + + + {previewResult && ( + <> + + + {previewResult.resultTotals.fired}, + lookback: previewOptions.find((e) => e.value === previewLookbackInterval) + ?.shortText, + }} + />{' '} + {previewResult.numberOfGroups}, + groupName: alertParams.nodeType, + plural: previewResult.numberOfGroups !== 1 ? 's' : '', + }} + /> + + + )} + {previewIntervalError && ( + <> + + + check every, + }} + /> + + + )} + {previewError && ( + <> + + + + + + )} + + + ); }; diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/metric.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/alerting/inventory/metric.tsx rename to x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/node_type.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/alerting/inventory/node_type.tsx rename to x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/validation.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/alerting/inventory/validation.tsx rename to x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/metric_inventory_threshold_alert_type.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts similarity index 73% rename from x-pack/plugins/infra/public/components/alerting/inventory/metric_inventory_threshold_alert_type.ts rename to x-pack/plugins/infra/public/alerting/inventory/index.ts index 0cb564ec2194e..7503e5673fcd9 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/metric_inventory_threshold_alert_type.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts @@ -6,19 +6,20 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types'; -import { validateMetricThreshold } from './validation'; +import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/inventory_metric_threshold/types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; +import { validateMetricThreshold } from './components/validation'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/inventory_metric_threshold/types'; -export function getInventoryMetricAlertType(): AlertTypeModel { +export function createInventoryMetricAlertType(): AlertTypeModel { return { id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, name: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertName', { defaultMessage: 'Inventory', }), iconClass: 'bell', - alertParamsExpression: React.lazy(() => import('./expression')), + alertParamsExpression: React.lazy(() => import('./components/expression')), validate: validateMetricThreshold, defaultActionMessage: i18n.translate( 'xpack.infra.metrics.alerting.inventory.threshold.defaultActionMessage', diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index febf849ccc943..3c3351f4ddd76 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -5,8 +5,8 @@ */ import { debounce, pick } from 'lodash'; +import { Unit } from '@elastic/datemath'; import * as rt from 'io-ts'; -import { HttpSetup } from 'src/core/public'; import React, { ChangeEvent, useCallback, useMemo, useEffect, useState } from 'react'; import { EuiSpacer, @@ -24,15 +24,18 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { + previewOptions, + firedTimeLabel, + firedTimesLabel, + getMetricThresholdAlertPreview as getAlertPreview, +} from '../../common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getIntervalInSeconds } from '../../../../server/utils/get_interval_in_seconds'; import { Comparator, Aggregators, - INFRA_ALERT_PREVIEW_PATH, - alertPreviewRequestParamsRT, alertPreviewSuccessResponsePayloadRT, - METRIC_THRESHOLD_ALERT_TYPE_ID, } from '../../../../common/alerting/metrics'; import { ForLastExpression, @@ -79,22 +82,6 @@ const defaultExpression = { timeUnit: 'm', } as MetricExpression; -async function getAlertPreview({ - fetch, - params, -}: { - fetch: HttpSetup['fetch']; - params: rt.TypeOf; -}): Promise> { - return await fetch(`${INFRA_ALERT_PREVIEW_PATH}`, { - method: 'POST', - body: JSON.stringify({ - ...params, - alertType: METRIC_THRESHOLD_ALERT_TYPE_ID, - }), - }); -} - export const Expressions: React.FC = (props) => { const { setAlertParams, alertParams, errors, alertsContext, alertInterval } = props; const { source, createDerivedIndexPattern } = useSourceViaHttp({ @@ -275,7 +262,7 @@ export const Expressions: React.FC = (props) => { params: { ...pick(alertParams, 'criteria', 'groupBy', 'filterQuery'), sourceId: alertParams.sourceId, - lookback: previewLookbackInterval as 'h' | 'd' | 'w' | 'M', + lookback: previewLookbackInterval as Unit, alertInterval, }, }); @@ -319,11 +306,12 @@ export const Expressions: React.FC = (props) => { }, [previewLookbackInterval, alertInterval]); const isPreviewDisabled = useMemo(() => { + if (previewIntervalError) return true; const validationResult = validateMetricThreshold({ criteria: alertParams.criteria } as any); const hasValidationErrors = Object.values(validationResult.errors).some((result) => Object.values(result).some((arr) => Array.isArray(arr) && arr.length) ); - return hasValidationErrors || previewIntervalError; + return hasValidationErrors; }, [alertParams.criteria, previewIntervalError]); return ( @@ -600,52 +588,6 @@ export const Expressions: React.FC = (props) => { ); }; -const previewOptions = [ - { - value: 'h', - text: i18n.translate('xpack.infra.metrics.alertFlyout.lastHourLabel', { - defaultMessage: 'Last hour', - }), - shortText: i18n.translate('xpack.infra.metrics.alertFlyout.hourLabel', { - defaultMessage: 'hour', - }), - }, - { - value: 'd', - text: i18n.translate('xpack.infra.metrics.alertFlyout.lastDayLabel', { - defaultMessage: 'Last day', - }), - shortText: i18n.translate('xpack.infra.metrics.alertFlyout.dayLabel', { - defaultMessage: 'day', - }), - }, - { - value: 'w', - text: i18n.translate('xpack.infra.metrics.alertFlyout.lastWeekLabel', { - defaultMessage: 'Last week', - }), - shortText: i18n.translate('xpack.infra.metrics.alertFlyout.weekLabel', { - defaultMessage: 'week', - }), - }, - { - value: 'M', - text: i18n.translate('xpack.infra.metrics.alertFlyout.lastMonthLabel', { - defaultMessage: 'Last month', - }), - shortText: i18n.translate('xpack.infra.metrics.alertFlyout.monthLabel', { - defaultMessage: 'month', - }), - }, -]; - -const firedTimeLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTime', { - defaultMessage: 'time', -}); -const firedTimesLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTimes', { - defaultMessage: 'times', -}); - // required for dynamic import // eslint-disable-next-line import/no-default-export export default Expressions; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 05296fbf6b0a3..ab7f41e3066b8 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -29,7 +29,7 @@ import { WaffleOptionsProvider } from './inventory_view/hooks/use_waffle_options import { WaffleTimeProvider } from './inventory_view/hooks/use_waffle_time'; import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters'; -import { InventoryAlertDropdown } from '../../components/alerting/inventory/alert_dropdown'; +import { InventoryAlertDropdown } from '../../alerting/inventory/components/alert_dropdown'; import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components/alert_dropdown'; const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx index 3441b6bf2c1b9..d913261521383 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo, useState } from 'react'; -import { AlertFlyout } from '../../../../../components/alerting/inventory/alert_flyout'; +import { AlertFlyout } from '../../../../../alerting/inventory/components/alert_flyout'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib'; import { getNodeDetailUrl, getNodeLogsUrl } from '../../../../link_to'; import { createUptimeLink } from '../../lib/create_uptime_link'; diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index b3765db43335a..496e788efc060 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -13,7 +13,7 @@ import { } from 'kibana/public'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { createMetricThresholdAlertType } from './alerting/metric_threshold'; -import { getInventoryMetricAlertType } from './components/alerting/inventory/metric_inventory_threshold_alert_type'; +import { createInventoryMetricAlertType } from './alerting/inventory'; import { getAlertType as getLogsAlertType } from './components/alerting/logs/log_threshold_alert_type'; import { registerStartSingleton } from './legacy_singletons'; import { registerFeatures } from './register_feature'; @@ -29,7 +29,7 @@ export class Plugin setup(core: CoreSetup, pluginsSetup: ClientPluginsSetup) { registerFeatures(pluginsSetup.home); - pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getInventoryMetricAlertType()); + pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(createInventoryMetricAlertType()); pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getLogsAlertType()); pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(createMetricThresholdAlertType()); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts new file mode 100644 index 0000000000000..c55f50e229b69 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts @@ -0,0 +1,136 @@ +/* + * 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, last } from 'lodash'; +import moment from 'moment'; +import { + InfraDatabaseSearchResponse, + CallWithRequestParams, +} from '../../adapters/framework/adapter_types'; +import { Comparator, InventoryMetricConditions } from './types'; +import { AlertServices } from '../../../../../alerts/server'; +import { InfraSnapshot } from '../../snapshot'; +import { parseFilterQuery } from '../../../utils/serialized_query'; +import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; +import { InfraTimerangeInput } from '../../../../common/http_api/snapshot_api'; +import { InfraSourceConfiguration } from '../../sources'; + +interface ConditionResult { + shouldFire: boolean | boolean[]; + currentValue?: number | null; + metric: string; + isNoData: boolean; + isError: boolean; +} + +export const evaluateCondition = async ( + condition: InventoryMetricConditions, + nodeType: InventoryItemType, + sourceConfiguration: InfraSourceConfiguration, + callCluster: AlertServices['callCluster'], + filterQuery?: string, + lookbackSize?: number +): Promise> => { + const { comparator, metric } = condition; + let { threshold } = condition; + + const timerange = { + to: Date.now(), + from: moment().subtract(condition.timeSize, condition.timeUnit).toDate().getTime(), + interval: condition.timeUnit, + } as InfraTimerangeInput; + if (lookbackSize) { + timerange.lookbackSize = lookbackSize; + } + + const currentValues = await getData( + callCluster, + nodeType, + metric, + timerange, + sourceConfiguration, + filterQuery + ); + + threshold = threshold.map((n) => convertMetricValue(metric, n)); + + const comparisonFunction = comparatorMap[comparator]; + + return mapValues(currentValues, (value) => ({ + shouldFire: + value !== undefined && + value !== null && + (Array.isArray(value) + ? value.map((v) => comparisonFunction(Number(v), threshold)) + : comparisonFunction(value, threshold)), + metric, + isNoData: value === null, + isError: value === undefined, + ...(!Array.isArray(value) ? { currentValue: value } : {}), + })); +}; + +const getData = async ( + callCluster: AlertServices['callCluster'], + nodeType: InventoryItemType, + metric: SnapshotMetricType, + timerange: InfraTimerangeInput, + sourceConfiguration: InfraSourceConfiguration, + filterQuery?: string +) => { + const snapshot = new InfraSnapshot(); + const esClient = ( + options: CallWithRequestParams + ): Promise> => callCluster('search', options); + + const options = { + filterQuery: parseFilterQuery(filterQuery), + nodeType, + groupBy: [], + sourceConfiguration, + metric: { type: metric }, + timerange, + includeTimeseries: Boolean(timerange.lookbackSize), + }; + + const { nodes } = await snapshot.getNodes(esClient, options); + + return nodes.reduce((acc, n) => { + const nodePathItem = last(n.path); + if (n.metric?.value && n.metric?.timeseries) { + const { timeseries } = n.metric; + const values = timeseries.rows.map((row) => row.metric_0) as Array; + acc[nodePathItem.label] = values; + } else { + acc[nodePathItem.label] = n.metric && n.metric.value; + } + return acc; + }, {} as Record | undefined | null>); +}; + +const comparatorMap = { + [Comparator.BETWEEN]: (value: number, [a, b]: number[]) => + value >= Math.min(a, b) && value <= Math.max(a, b), + // `threshold` is always an array of numbers in case the BETWEEN comparator is + // used; all other compartors will just destructure the first value in the array + [Comparator.GT]: (a: number, [b]: number[]) => a > b, + [Comparator.LT]: (a: number, [b]: number[]) => a < b, + [Comparator.OUTSIDE_RANGE]: (value: number, [a, b]: number[]) => value < a || value > b, + [Comparator.GT_OR_EQ]: (a: number, [b]: number[]) => a >= b, + [Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b, +}; + +// Some metrics in the UI are in a different unit that what we store in ES. +const convertMetricValue = (metric: SnapshotMetricType, value: number) => { + if (converters[metric]) { + return converters[metric](value); + } else { + return value; + } +}; +const converters: Record number> = { + cpu: (n) => Number(n) / 100, + memory: (n) => Number(n) / 100, +}; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 5a34a6665e781..99e653b2d6789 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -3,27 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { mapValues, last, get } from 'lodash'; +import { first, get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import moment from 'moment'; -import { - InfraDatabaseSearchResponse, - CallWithRequestParams, -} from '../../adapters/framework/adapter_types'; -import { Comparator, AlertStates, InventoryMetricConditions } from './types'; -import { AlertServices, AlertExecutorOptions } from '../../../../../alerts/server'; -import { InfraSnapshot } from '../../snapshot'; -import { parseFilterQuery } from '../../../utils/serialized_query'; +import { AlertStates, InventoryMetricConditions } from './types'; +import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; -import { InfraTimerangeInput } from '../../../../common/http_api/snapshot_api'; -import { InfraSourceConfiguration } from '../../sources'; import { InfraBackendLibs } from '../../infra_types'; import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats'; import { createFormatter } from '../../../../common/formatters'; +import { evaluateCondition } from './evaluate_condition'; interface InventoryMetricThresholdParams { criteria: InventoryMetricConditions[]; - groupBy: string | undefined; filterQuery: string | undefined; nodeType: InventoryItemType; sourceId?: string; @@ -41,11 +32,13 @@ export const createInventoryMetricThresholdExecutor = ( ); const results = await Promise.all( - criteria.map((c) => evaluateCondtion(c, nodeType, source.configuration, services, filterQuery)) + criteria.map((c) => + evaluateCondition(c, nodeType, source.configuration, services.callCluster, filterQuery) + ) ); - const invenotryItems = Object.keys(results[0]); - for (const item of invenotryItems) { + const inventoryItems = Object.keys(first(results)); + for (const item of inventoryItems) { const alertInstance = services.alertInstanceFactory(`${alertId}-${item}`); // AND logic; all criteria must be across the threshold const shouldAlertFire = results.every((result) => result[item].shouldFire); @@ -79,93 +72,6 @@ export const createInventoryMetricThresholdExecutor = ( } }; -interface ConditionResult { - shouldFire: boolean; - currentValue?: number | null; - isNoData: boolean; - isError: boolean; -} - -const evaluateCondtion = async ( - condition: InventoryMetricConditions, - nodeType: InventoryItemType, - sourceConfiguration: InfraSourceConfiguration, - services: AlertServices, - filterQuery?: string -): Promise> => { - const { comparator, metric } = condition; - let { threshold } = condition; - - const currentValues = await getData( - services, - nodeType, - metric, - { - to: Date.now(), - from: moment().subtract(condition.timeSize, condition.timeUnit).toDate().getTime(), - interval: condition.timeUnit, - }, - sourceConfiguration, - filterQuery - ); - - threshold = threshold.map((n) => convertMetricValue(metric, n)); - - const comparisonFunction = comparatorMap[comparator]; - - return mapValues(currentValues, (value) => ({ - shouldFire: value !== undefined && value !== null && comparisonFunction(value, threshold), - metric, - currentValue: value, - isNoData: value === null, - isError: value === undefined, - })); -}; - -const getData = async ( - services: AlertServices, - nodeType: InventoryItemType, - metric: SnapshotMetricType, - timerange: InfraTimerangeInput, - sourceConfiguration: InfraSourceConfiguration, - filterQuery?: string -) => { - const snapshot = new InfraSnapshot(); - const esClient = ( - options: CallWithRequestParams - ): Promise> => - services.callCluster('search', options); - - const options = { - filterQuery: parseFilterQuery(filterQuery), - nodeType, - groupBy: [], - sourceConfiguration, - metric: { type: metric }, - timerange, - }; - - const { nodes } = await snapshot.getNodes(esClient, options); - - return nodes.reduce((acc, n) => { - const nodePathItem = last(n.path); - acc[nodePathItem.label] = n.metric && n.metric.value; - return acc; - }, {} as Record); -}; - -const comparatorMap = { - [Comparator.BETWEEN]: (value: number, [a, b]: number[]) => - value >= Math.min(a, b) && value <= Math.max(a, b), - // `threshold` is always an array of numbers in case the BETWEEN comparator is - // used; all other compartors will just destructure the first value in the array - [Comparator.GT]: (a: number, [b]: number[]) => a > b, - [Comparator.LT]: (a: number, [b]: number[]) => a < b, - [Comparator.OUTSIDE_RANGE]: (value: number, [a, b]: number[]) => value < a || value > b, - [Comparator.GT_OR_EQ]: (a: number, [b]: number[]) => a >= b, - [Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b, -}; - const mapToConditionsLookup = ( list: any[], mapFn: (value: any, index: number, array: any[]) => unknown @@ -184,19 +90,6 @@ export const FIRED_ACTIONS = { }), }; -// Some metrics in the UI are in a different unit that what we store in ES. -const convertMetricValue = (metric: SnapshotMetricType, value: number) => { - if (converters[metric]) { - return converters[metric](value); - } else { - return value; - } -}; -const converters: Record number> = { - cpu: (n) => Number(n) / 100, - memory: (n) => Number(n) / 100, -}; - const formatMetric = (metric: SnapshotMetricType, value: number) => { // if (SnapshotCustomMetricInputRT.is(metric)) { // const formatter = createFormatterForMetric(metric); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert.ts new file mode 100644 index 0000000000000..6e8c624e61c49 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert.ts @@ -0,0 +1,83 @@ +/* + * 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 { Unit } from '@elastic/datemath'; +import { first } from 'lodash'; +import { InventoryMetricConditions } from './types'; +import { IScopedClusterClient } from '../../../../../../../src/core/server'; +import { InfraSource } from '../../../../common/http_api/source_api'; +import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; +import { InventoryItemType } from '../../../../common/inventory_models/types'; +import { evaluateCondition } from './evaluate_condition'; + +interface InventoryMetricThresholdParams { + criteria: InventoryMetricConditions[]; + filterQuery: string | undefined; + nodeType: InventoryItemType; + sourceId?: string; +} + +interface PreviewInventoryMetricThresholdAlertParams { + callCluster: IScopedClusterClient['callAsCurrentUser']; + params: InventoryMetricThresholdParams; + config: InfraSource['configuration']; + lookback: Unit; + alertInterval: string; +} + +export const previewInventoryMetricThresholdAlert = async ({ + callCluster, + params, + config, + lookback, + alertInterval, +}: PreviewInventoryMetricThresholdAlertParams) => { + const { criteria, filterQuery, nodeType } = params as InventoryMetricThresholdParams; + + const { timeSize, timeUnit } = criteria[0]; + const bucketInterval = `${timeSize}${timeUnit}`; + const bucketIntervalInSeconds = getIntervalInSeconds(bucketInterval); + + const lookbackInterval = `1${lookback}`; + const lookbackIntervalInSeconds = getIntervalInSeconds(lookbackInterval); + const lookbackSize = Math.ceil(lookbackIntervalInSeconds / bucketIntervalInSeconds); + + const alertIntervalInSeconds = getIntervalInSeconds(alertInterval); + const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds; + + const results = await Promise.all( + criteria.map((c) => + evaluateCondition(c, nodeType, config, callCluster, filterQuery, lookbackSize) + ) + ); + + const inventoryItems = Object.keys(first(results)); + const previewResults = inventoryItems.map((item) => { + const isNoData = results.some((result) => result[item].isNoData); + if (isNoData) { + return null; + } + const isError = results.some((result) => result[item].isError); + if (isError) { + return undefined; + } + + const numberOfResultBuckets = lookbackSize; + const numberOfExecutionBuckets = Math.floor(numberOfResultBuckets / alertResultsPerExecution); + return [...Array(numberOfExecutionBuckets)].reduce( + (totalFired, _, i) => + totalFired + + (results.every((result) => { + const shouldFire = result[item].shouldFire as boolean[]; + return shouldFire[Math.floor(i * alertResultsPerExecution)]; + }) + ? 1 + : 0), + 0 + ); + }); + + return previewResults; +}; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts index 73ee1ab6b7615..ec1caad30a4d7 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { Unit } from '@elastic/datemath'; import { SnapshotMetricType } from '../../../../common/inventory_models/types'; export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold'; @@ -23,12 +24,10 @@ export enum AlertStates { ERROR, } -export type TimeUnit = 's' | 'm' | 'h' | 'd'; - export interface InventoryMetricConditions { metric: SnapshotMetricType; timeSize: number; - timeUnit: TimeUnit; + timeUnit: Unit; sourceId?: string; threshold: number[]; comparator: Comparator; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts index 7aa8367f7678c..52637d52175a4 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts @@ -5,6 +5,7 @@ */ import { first, zip } from 'lodash'; +import { Unit } from '@elastic/datemath'; import { TOO_MANY_BUCKETS_PREVIEW_EXCEPTION, isTooManyBucketsPreviewException, @@ -25,7 +26,7 @@ interface PreviewMetricThresholdAlertParams { filterQuery: string | undefined; }; config: InfraSource['configuration']; - lookback: 'h' | 'd' | 'w' | 'M'; + lookback: Unit; alertInterval: string; end?: number; overrideLookbackIntervalInSeconds?: number; diff --git a/x-pack/plugins/infra/server/routes/alerting/preview.ts b/x-pack/plugins/infra/server/routes/alerting/preview.ts index f4eed041481f6..d11425a4f4cb0 100644 --- a/x-pack/plugins/infra/server/routes/alerting/preview.ts +++ b/x-pack/plugins/infra/server/routes/alerting/preview.ts @@ -12,8 +12,10 @@ import { alertPreviewRequestParamsRT, alertPreviewSuccessResponsePayloadRT, MetricThresholdAlertPreviewRequestParams, + InventoryAlertPreviewRequestParams, } from '../../../common/alerting/metrics'; import { createValidationFunction } from '../../../common/runtime_types'; +import { previewInventoryMetricThresholdAlert } from '../../lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert'; import { previewMetricThresholdAlert } from '../../lib/alerting/metric_threshold/preview_metric_threshold_alert'; import { InfraBackendLibs } from '../../lib/infra_types'; @@ -76,8 +78,35 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) }); } case METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID: { - // TODO: Add inventory preview functionality - return response.ok({}); + const { nodeType } = request.body as InventoryAlertPreviewRequestParams; + const previewResult = await previewInventoryMetricThresholdAlert({ + callCluster, + params: { criteria, filterQuery, nodeType }, + lookback, + config: source.configuration, + alertInterval, + }); + + const numberOfGroups = previewResult.length; + const resultTotals = previewResult.reduce( + (totals, groupResult) => { + if (groupResult === null) return { ...totals, noData: totals.noData + 1 }; + if (isNaN(groupResult)) return { ...totals, error: totals.error + 1 }; + return { ...totals, fired: totals.fired + groupResult }; + }, + { + fired: 0, + noData: 0, + error: 0, + } + ); + + return response.ok({ + body: alertPreviewSuccessResponsePayloadRT.encode({ + numberOfGroups, + resultTotals, + }), + }); } default: throw new Error('Unknown alert type'); From e2ab94060a6156ebe7170469fbfd22ec8addd87d Mon Sep 17 00:00:00 2001 From: Brandon Morelli Date: Tue, 23 Jun 2020 17:22:32 -0700 Subject: [PATCH 27/36] [Obs] Update Observability landing page text (#69727) --- x-pack/plugins/observability/public/pages/home/index.tsx | 4 ++-- x-pack/plugins/observability/public/pages/home/section.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 696361393ef82..91e7e2759b824 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -92,7 +92,7 @@ export const Home = () => {

{i18n.translate('xpack.observability.home.sectionTitle', { - defaultMessage: 'Observability built on the Elastic Stack', + defaultMessage: 'Unified visibility across your entire ecosystem', })}

@@ -100,7 +100,7 @@ export const Home = () => { {i18n.translate('xpack.observability.home.sectionsubtitle', { defaultMessage: - 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + 'Monitor, analyze, and react to events happening anywhere in your environment by bringing logs, metrics, and traces together at scale in a single stack.', })}
diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts index a2b82c31bf2ab..d33571a16ccb7 100644 --- a/x-pack/plugins/observability/public/pages/home/section.ts +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -23,7 +23,7 @@ export const appsSection: ISection[] = [ icon: 'logoLogging', description: i18n.translate('xpack.observability.section.apps.logs.description', { defaultMessage: - 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', + 'Centralize logs from any source. Search, tail, automate anomaly detection, and visualize trends so you can take action quicker.', }), }, { @@ -34,7 +34,7 @@ export const appsSection: ISection[] = [ icon: 'logoAPM', description: i18n.translate('xpack.observability.section.apps.apm.description', { defaultMessage: - 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', + 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', }), }, { @@ -45,7 +45,7 @@ export const appsSection: ISection[] = [ icon: 'logoMetrics', description: i18n.translate('xpack.observability.section.apps.metrics.description', { defaultMessage: - 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', + 'Analyze metrics from your infrastructure, apps, and services. Discover trends, forecast behavior, get alerts on anomalies, and more.', }), }, { @@ -56,7 +56,7 @@ export const appsSection: ISection[] = [ icon: 'logoUptime', description: i18n.translate('xpack.observability.section.apps.uptime.description', { defaultMessage: - 'React to availability issues across your apps and services before they affect users.', + 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users’ experience.', }), }, ]; From 33fb3e832cc1cf49741669ff86f45b8944a0ab2e Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Wed, 24 Jun 2020 08:05:02 +0200 Subject: [PATCH 28/36] Support deep links inside of `RelayState` for SAML IdP initiated login. (#69401) --- x-pack/dev-tools/jest/setup/polyfills.js | 6 +- .../security/common/is_internal_url.test.ts | 88 ++++++ .../security/common/is_internal_url.ts | 38 +++ x-pack/plugins/security/common/parse_next.ts | 20 +- .../authentication/providers/saml.test.ts | 276 ++++++++++++++++++ .../server/authentication/providers/saml.ts | 53 +++- x-pack/plugins/security/server/config.test.ts | 16 + x-pack/plugins/security/server/config.ts | 1 + .../server/routes/authentication/saml.test.ts | 35 ++- .../server/routes/authentication/saml.ts | 14 +- .../apis/login_selector.ts | 90 +++++- .../login_selector_api_integration/config.ts | 7 +- 12 files changed, 609 insertions(+), 35 deletions(-) create mode 100644 x-pack/plugins/security/common/is_internal_url.test.ts create mode 100644 x-pack/plugins/security/common/is_internal_url.ts diff --git a/x-pack/dev-tools/jest/setup/polyfills.js b/x-pack/dev-tools/jest/setup/polyfills.js index 5ecee2e3ad0d3..822802f3dacb7 100644 --- a/x-pack/dev-tools/jest/setup/polyfills.js +++ b/x-pack/dev-tools/jest/setup/polyfills.js @@ -17,5 +17,7 @@ const MutationObserver = require('mutation-observer'); Object.defineProperty(window, 'MutationObserver', { value: MutationObserver }); require('whatwg-fetch'); -const URL = { createObjectURL: () => '' }; -Object.defineProperty(window, 'URL', { value: URL }); + +if (!global.URL.hasOwnProperty('createObjectURL')) { + Object.defineProperty(global.URL, 'createObjectURL', { value: () => '' }); +} diff --git a/x-pack/plugins/security/common/is_internal_url.test.ts b/x-pack/plugins/security/common/is_internal_url.test.ts new file mode 100644 index 0000000000000..7e9f63f069fd0 --- /dev/null +++ b/x-pack/plugins/security/common/is_internal_url.test.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 { isInternalURL } from './is_internal_url'; + +describe('isInternalURL', () => { + describe('with basePath defined', () => { + const basePath = '/iqf'; + + it('should return `true `if URL includes hash fragment', () => { + const href = `${basePath}/app/kibana#/discover/New-Saved-Search`; + expect(isInternalURL(href, basePath)).toBe(true); + }); + + it('should return `false` if URL includes a protocol/hostname', () => { + const href = `https://example.com${basePath}/app/kibana`; + expect(isInternalURL(href, basePath)).toBe(false); + }); + + it('should return `false` if URL includes a port', () => { + const href = `http://localhost:5601${basePath}/app/kibana`; + expect(isInternalURL(href, basePath)).toBe(false); + }); + + it('should return `false` if URL does not specify protocol', () => { + const hrefWithTwoSlashes = `/${basePath}/app/kibana`; + expect(isInternalURL(hrefWithTwoSlashes)).toBe(false); + + const hrefWithThreeSlashes = `//${basePath}/app/kibana`; + expect(isInternalURL(hrefWithThreeSlashes)).toBe(false); + }); + + it('should return `true` if URL starts with a basepath', () => { + for (const href of [basePath, `${basePath}/`, `${basePath}/login`, `${basePath}/login/`]) { + expect(isInternalURL(href, basePath)).toBe(true); + } + }); + + it('should return `false` if URL does not start with basePath', () => { + for (const href of [ + '/notbasepath/app/kibana', + `${basePath}_/login`, + basePath.slice(1), + `${basePath.slice(1)}/app/kibana`, + ]) { + expect(isInternalURL(href, basePath)).toBe(false); + } + }); + + it('should return `true` if relative path does not escape base path', () => { + const href = `${basePath}/app/kibana/../../management`; + expect(isInternalURL(href, basePath)).toBe(true); + }); + + it('should return `false` if relative path escapes base path', () => { + const href = `${basePath}/app/kibana/../../../management`; + expect(isInternalURL(href, basePath)).toBe(false); + }); + }); + + describe('without basePath defined', () => { + it('should return `true `if URL includes hash fragment', () => { + const href = '/app/kibana#/discover/New-Saved-Search'; + expect(isInternalURL(href)).toBe(true); + }); + + it('should return `false` if URL includes a protocol/hostname', () => { + const href = 'https://example.com/app/kibana'; + expect(isInternalURL(href)).toBe(false); + }); + + it('should return `false` if URL includes a port', () => { + const href = 'http://localhost:5601/app/kibana'; + expect(isInternalURL(href)).toBe(false); + }); + + it('should return `false` if URL does not specify protocol', () => { + const hrefWithTwoSlashes = `//app/kibana`; + expect(isInternalURL(hrefWithTwoSlashes)).toBe(false); + + const hrefWithThreeSlashes = `///app/kibana`; + expect(isInternalURL(hrefWithThreeSlashes)).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/security/common/is_internal_url.ts b/x-pack/plugins/security/common/is_internal_url.ts new file mode 100644 index 0000000000000..e83839bf7d34b --- /dev/null +++ b/x-pack/plugins/security/common/is_internal_url.ts @@ -0,0 +1,38 @@ +/* + * 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 { parse } from 'url'; + +export function isInternalURL(url: string, basePath = '') { + const { protocol, hostname, port, pathname } = parse( + url, + false /* parseQueryString */, + true /* slashesDenoteHost */ + ); + + // We should explicitly compare `protocol`, `port` and `hostname` to null to make sure these are not + // detected in the URL at all. For example `hostname` can be empty string for Node URL parser, but + // browser (because of various bwc reasons) processes URL differently (e.g. `///abc.com` - for browser + // hostname is `abc.com`, but for Node hostname is an empty string i.e. everything between schema (`//`) + // and the first slash that belongs to path. + if (protocol !== null || hostname !== null || port !== null) { + return false; + } + + if (basePath) { + // Now we need to normalize URL to make sure any relative path segments (`..`) cannot escape expected + // base path. We can rely on `URL` with a localhost to automatically "normalize" the URL. + const normalizedPathname = new URL(String(pathname), 'https://localhost').pathname; + return ( + // Normalized pathname can add a leading slash, but we should also make sure it's included in + // the original URL too + pathname?.startsWith('/') && + (normalizedPathname === basePath || normalizedPathname.startsWith(`${basePath}/`)) + ); + } + + return true; +} diff --git a/x-pack/plugins/security/common/parse_next.ts b/x-pack/plugins/security/common/parse_next.ts index 7cbe335825a5a..7ce0de05ad526 100644 --- a/x-pack/plugins/security/common/parse_next.ts +++ b/x-pack/plugins/security/common/parse_next.ts @@ -5,6 +5,7 @@ */ import { parse } from 'url'; +import { isInternalURL } from './is_internal_url'; export function parseNext(href: string, basePath = '') { const { query, hash } = parse(href, true); @@ -20,23 +21,8 @@ export function parseNext(href: string, basePath = '') { } // validate that `next` is not attempting a redirect to somewhere - // outside of this Kibana install - const { protocol, hostname, port, pathname } = parse( - next, - false /* parseQueryString */, - true /* slashesDenoteHost */ - ); - - // We should explicitly compare `protocol`, `port` and `hostname` to null to make sure these are not - // detected in the URL at all. For example `hostname` can be empty string for Node URL parser, but - // browser (because of various bwc reasons) processes URL differently (e.g. `///abc.com` - for browser - // hostname is `abc.com`, but for Node hostname is an empty string i.e. everything between schema (`//`) - // and the first slash that belongs to path. - if (protocol !== null || hostname !== null || port !== null) { - return `${basePath}/`; - } - - if (!String(pathname).startsWith(basePath)) { + // outside of this Kibana install. + if (!isInternalURL(next, basePath)) { return `${basePath}/`; } diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index 461ad3e38eca5..f8e735a658a20 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -110,6 +110,51 @@ describe('SAMLAuthenticationProvider', () => { ); }); + it('gets token and redirects user to the requested URL if SAML Response is valid ignoring Relay State.', async () => { + const request = httpServerMock.createKibanaRequest(); + + mockOptions.client.callAsInternalUser.mockResolvedValue({ + username: 'user', + access_token: 'some-token', + refresh_token: 'some-refresh-token', + }); + + provider = new SAMLAuthenticationProvider(mockOptions, { + realm: 'test-realm', + maxRedirectURLSize: new ByteSizeValue(100), + useRelayStateDeepLink: true, + }); + await expect( + provider.login( + request, + { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response-xml', + relayState: `${mockOptions.basePath.serverBasePath}/app/some-app#some-deep-link`, + }, + { + requestId: 'some-request-id', + redirectURL: '/test-base-path/some-path#some-app', + realm: 'test-realm', + } + ) + ).resolves.toEqual( + AuthenticationResult.redirectTo('/test-base-path/some-path#some-app', { + state: { + username: 'user', + accessToken: 'some-token', + refreshToken: 'some-refresh-token', + realm: 'test-realm', + }, + }) + ); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( + 'shield.samlAuthenticate', + { body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } } + ); + }); + it('fails if SAML Response payload is presented but state does not contain SAML Request token.', async () => { const request = httpServerMock.createKibanaRequest(); @@ -178,6 +223,45 @@ describe('SAMLAuthenticationProvider', () => { ); }); + it('redirects to the default location if state contains empty redirect URL ignoring Relay State.', async () => { + const request = httpServerMock.createKibanaRequest(); + + mockOptions.client.callAsInternalUser.mockResolvedValue({ + access_token: 'user-initiated-login-token', + refresh_token: 'user-initiated-login-refresh-token', + }); + + provider = new SAMLAuthenticationProvider(mockOptions, { + realm: 'test-realm', + maxRedirectURLSize: new ByteSizeValue(100), + useRelayStateDeepLink: true, + }); + await expect( + provider.login( + request, + { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response-xml', + relayState: `${mockOptions.basePath.serverBasePath}/app/some-app#some-deep-link`, + }, + { requestId: 'some-request-id', redirectURL: '', realm: 'test-realm' } + ) + ).resolves.toEqual( + AuthenticationResult.redirectTo('/base-path/', { + state: { + accessToken: 'user-initiated-login-token', + refreshToken: 'user-initiated-login-refresh-token', + realm: 'test-realm', + }, + }) + ); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( + 'shield.samlAuthenticate', + { body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } } + ); + }); + it('redirects to the default location if state is not presented.', async () => { const request = httpServerMock.createKibanaRequest(); @@ -231,6 +315,133 @@ describe('SAMLAuthenticationProvider', () => { ); }); + describe('IdP initiated login', () => { + beforeEach(() => { + mockOptions.basePath.get.mockReturnValue(mockOptions.basePath.serverBasePath); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockImplementation(() => + Promise.resolve(mockAuthenticatedUser()) + ); + mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); + + mockOptions.client.callAsInternalUser.mockResolvedValue({ + username: 'user', + access_token: 'valid-token', + refresh_token: 'valid-refresh-token', + }); + + provider = new SAMLAuthenticationProvider(mockOptions, { + realm: 'test-realm', + maxRedirectURLSize: new ByteSizeValue(100), + useRelayStateDeepLink: true, + }); + }); + + it('redirects to the home page if `useRelayStateDeepLink` is set to `false`.', async () => { + provider = new SAMLAuthenticationProvider(mockOptions, { + realm: 'test-realm', + maxRedirectURLSize: new ByteSizeValue(100), + useRelayStateDeepLink: false, + }); + + await expect( + provider.login(httpServerMock.createKibanaRequest({ headers: {} }), { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response-xml', + relayState: `${mockOptions.basePath.serverBasePath}/app/some-app#some-deep-link`, + }) + ).resolves.toEqual( + AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + state: { + username: 'user', + accessToken: 'valid-token', + refreshToken: 'valid-refresh-token', + realm: 'test-realm', + }, + }) + ); + }); + + it('redirects to the home page if `relayState` is not specified.', async () => { + await expect( + provider.login(httpServerMock.createKibanaRequest({ headers: {} }), { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response-xml', + }) + ).resolves.toEqual( + AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + state: { + username: 'user', + accessToken: 'valid-token', + refreshToken: 'valid-refresh-token', + realm: 'test-realm', + }, + }) + ); + }); + + it('redirects to the home page if `relayState` includes external URL', async () => { + await expect( + provider.login(httpServerMock.createKibanaRequest({ headers: {} }), { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response-xml', + relayState: `https://evil.com${mockOptions.basePath.serverBasePath}/app/some-app#some-deep-link`, + }) + ).resolves.toEqual( + AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + state: { + username: 'user', + accessToken: 'valid-token', + refreshToken: 'valid-refresh-token', + realm: 'test-realm', + }, + }) + ); + }); + + it('redirects to the home page if `relayState` includes URL that starts with double slashes', async () => { + await expect( + provider.login(httpServerMock.createKibanaRequest({ headers: {} }), { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response-xml', + relayState: `//${mockOptions.basePath.serverBasePath}/app/some-app#some-deep-link`, + }) + ).resolves.toEqual( + AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + state: { + username: 'user', + accessToken: 'valid-token', + refreshToken: 'valid-refresh-token', + realm: 'test-realm', + }, + }) + ); + }); + + it('redirects to the URL from the relay state.', async () => { + await expect( + provider.login(httpServerMock.createKibanaRequest({ headers: {} }), { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response-xml', + relayState: `${mockOptions.basePath.serverBasePath}/app/some-app#some-deep-link`, + }) + ).resolves.toEqual( + AuthenticationResult.redirectTo( + `${mockOptions.basePath.serverBasePath}/app/some-app#some-deep-link`, + { + state: { + username: 'user', + accessToken: 'valid-token', + refreshToken: 'valid-refresh-token', + realm: 'test-realm', + }, + } + ) + ); + }); + }); + describe('IdP initiated login with existing session', () => { it('returns `notHandled` if new SAML Response is rejected.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); @@ -377,6 +588,71 @@ describe('SAMLAuthenticationProvider', () => { }); }); + it(`redirects to the URL from relay state if new SAML Response is for the same user if ${description}.`, async () => { + const request = httpServerMock.createKibanaRequest({ headers: {} }); + const state = { + username: 'user', + accessToken: 'existing-token', + refreshToken: 'existing-refresh-token', + realm: 'test-realm', + }; + const authorization = `Bearer ${state.accessToken}`; + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockImplementation(() => response); + mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); + + mockOptions.client.callAsInternalUser.mockResolvedValue({ + username: 'user', + access_token: 'new-valid-token', + refresh_token: 'new-valid-refresh-token', + }); + + mockOptions.tokens.invalidate.mockResolvedValue(undefined); + + provider = new SAMLAuthenticationProvider(mockOptions, { + realm: 'test-realm', + maxRedirectURLSize: new ByteSizeValue(100), + useRelayStateDeepLink: true, + }); + + await expect( + provider.login( + request, + { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response-xml', + relayState: '/mock-server-basepath/app/some-app#some-deep-link', + }, + state + ) + ).resolves.toEqual( + AuthenticationResult.redirectTo('/mock-server-basepath/app/some-app#some-deep-link', { + state: { + username: 'user', + accessToken: 'new-valid-token', + refreshToken: 'new-valid-refresh-token', + realm: 'test-realm', + }, + }) + ); + + expectAuthenticateCall(mockOptions.client, { headers: { authorization } }); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( + 'shield.samlAuthenticate', + { + body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, + } + ); + + expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ + accessToken: state.accessToken, + refreshToken: state.refreshToken, + }); + }); + it(`redirects to \`overwritten_session\` if new SAML Response is for the another user if ${description}.`, async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); const state = { diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 3161144023c1f..9a4b7dd679ccb 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -7,6 +7,7 @@ import Boom from 'boom'; import { ByteSizeValue } from '@kbn/config-schema'; import { KibanaRequest } from '../../../../../../src/core/server'; +import { isInternalURL } from '../../../common/is_internal_url'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { canRedirectRequest } from '../can_redirect_request'; @@ -59,7 +60,7 @@ export enum SAMLLogin { */ type ProviderLoginAttempt = | { type: SAMLLogin.LoginInitiatedByUser; redirectURLPath?: string; redirectURLFragment?: string } - | { type: SAMLLogin.LoginWithSAMLResponse; samlResponse: string }; + | { type: SAMLLogin.LoginWithSAMLResponse; samlResponse: string; relayState?: string }; /** * Checks whether request query includes SAML request from IdP. @@ -98,9 +99,19 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { */ private readonly maxRedirectURLSize: ByteSizeValue; + /** + * Indicates if we should treat non-empty `RelayState` as a deep link in Kibana we should redirect + * user to after successful IdP initiated login. `RelayState` is ignored for SP initiated login. + */ + private readonly useRelayStateDeepLink: boolean; + constructor( protected readonly options: Readonly, - samlOptions?: Readonly<{ realm?: string; maxRedirectURLSize?: ByteSizeValue }> + samlOptions?: Readonly<{ + realm?: string; + maxRedirectURLSize?: ByteSizeValue; + useRelayStateDeepLink?: boolean; + }> ) { super(options); @@ -114,6 +125,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { this.realm = samlOptions.realm; this.maxRedirectURLSize = samlOptions.maxRedirectURLSize; + this.useRelayStateDeepLink = samlOptions.useRelayStateDeepLink ?? false; } /** @@ -148,14 +160,14 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { return this.captureRedirectURL(request, redirectURLPath, attempt.redirectURLFragment); } - const { samlResponse } = attempt; + const { samlResponse, relayState } = attempt; const authenticationResult = state ? await this.authenticateViaState(request, state) : AuthenticationResult.notHandled(); // Let's check if user is redirected to Kibana from IdP with valid SAMLResponse. if (authenticationResult.notHandled()) { - return await this.loginWithSAMLResponse(request, samlResponse, state); + return await this.loginWithSAMLResponse(request, samlResponse, relayState, state); } // If user has been authenticated via session or failed to do so because of expired access token, @@ -169,6 +181,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { return await this.loginWithNewSAMLResponse( request, samlResponse, + relayState, (authenticationResult.state || state) as ProviderState ); } @@ -290,11 +303,13 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { * initiated login. * @param request Request instance. * @param samlResponse SAMLResponse payload string. + * @param relayState RelayState payload string. * @param [state] Optional state object associated with the provider. */ private async loginWithSAMLResponse( request: KibanaRequest, samlResponse: string, + relayState?: string, state?: ProviderState | null ) { this.logger.debug('Trying to log in with SAML response payload.'); @@ -334,9 +349,29 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { }, }); + // IdP can pass `RelayState` with the deep link in Kibana during IdP initiated login and + // depending on the configuration we may need to redirect user to this URL. + let redirectURLFromRelayState; + if (isIdPInitiatedLogin && relayState) { + if (!this.useRelayStateDeepLink) { + this.options.logger.debug( + `"RelayState" is provided, but deep links support is not enabled for "${this.type}/${this.options.name}" provider.` + ); + } else if (!isInternalURL(relayState, this.options.basePath.serverBasePath)) { + this.options.logger.debug( + `"RelayState" is provided, but it is not a valid Kibana internal URL.` + ); + } else { + this.options.logger.debug( + `User will be redirected to the Kibana internal URL specified in "RelayState".` + ); + redirectURLFromRelayState = relayState; + } + } + this.logger.debug('Login has been performed with SAML response.'); return AuthenticationResult.redirectTo( - stateRedirectURL || `${this.options.basePath.get(request)}/`, + redirectURLFromRelayState || stateRedirectURL || `${this.options.basePath.get(request)}/`, { state: { username, accessToken, refreshToken, realm: this.realm } } ); } catch (err) { @@ -361,17 +396,23 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { * we'll forward user to a page with the respective warning. * @param request Request instance. * @param samlResponse SAMLResponse payload string. + * @param relayState RelayState payload string. * @param existingState State existing user session is based on. */ private async loginWithNewSAMLResponse( request: KibanaRequest, samlResponse: string, + relayState: string | undefined, existingState: ProviderState ) { this.logger.debug('Trying to log in with SAML response payload and existing valid session.'); // First let's try to authenticate via SAML Response payload. - const payloadAuthenticationResult = await this.loginWithSAMLResponse(request, samlResponse); + const payloadAuthenticationResult = await this.loginWithSAMLResponse( + request, + samlResponse, + relayState + ); if (payloadAuthenticationResult.failed() || payloadAuthenticationResult.notHandled()) { return payloadAuthenticationResult; } diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 4d0c9ca6c36e0..6ba33b2cccb7c 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -655,6 +655,7 @@ describe('config schema', () => { saml: { saml1: { order: 0, realm: 'saml1' }, saml2: { order: 1, realm: 'saml2', maxRedirectURLSize: '1kb' }, + saml3: { order: 2, realm: 'saml3', useRelayStateDeepLink: true }, }, }, }, @@ -670,6 +671,7 @@ describe('config schema', () => { "order": 0, "realm": "saml1", "showInSelector": true, + "useRelayStateDeepLink": false, }, "saml2": Object { "enabled": true, @@ -679,6 +681,17 @@ describe('config schema', () => { "order": 1, "realm": "saml2", "showInSelector": true, + "useRelayStateDeepLink": false, + }, + "saml3": Object { + "enabled": true, + "maxRedirectURLSize": ByteSizeValue { + "valueInBytes": 2048, + }, + "order": 2, + "realm": "saml3", + "showInSelector": true, + "useRelayStateDeepLink": true, }, }, } @@ -767,6 +780,7 @@ describe('config schema', () => { "order": 3, "realm": "saml3", "showInSelector": true, + "useRelayStateDeepLink": false, }, "saml1": Object { "enabled": true, @@ -776,6 +790,7 @@ describe('config schema', () => { "order": 1, "realm": "saml1", "showInSelector": true, + "useRelayStateDeepLink": false, }, "saml2": Object { "enabled": true, @@ -785,6 +800,7 @@ describe('config schema', () => { "order": 2, "realm": "saml2", "showInSelector": true, + "useRelayStateDeepLink": false, }, }, } diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 8a7865fa17efc..051a3d2ab1342 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -97,6 +97,7 @@ const providersConfigSchema = schema.object( ...getCommonProviderSchemaProperties(), realm: schema.string(), maxRedirectURLSize: schema.byteSize({ defaultValue: '2kb' }), + useRelayStateDeepLink: schema.boolean({ defaultValue: false }), }) ) ), diff --git a/x-pack/plugins/security/server/routes/authentication/saml.test.ts b/x-pack/plugins/security/server/routes/authentication/saml.test.ts index af63dfa2f4471..5f5161126f215 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.test.ts @@ -62,9 +62,9 @@ describe('SAML authentication routes', () => { `"[SAMLResponse]: expected value of type [string] but got [undefined]"` ); - expect(() => - bodyValidator.validate({ SAMLResponse: 'saml-response', UnknownArg: 'arg' }) - ).toThrowErrorMatchingInlineSnapshot(`"[UnknownArg]: definition for this key is missing"`); + expect(bodyValidator.validate({ SAMLResponse: 'saml-response', UnknownArg: 'arg' })).toEqual({ + SAMLResponse: 'saml-response', + }); }); it('returns 500 if authentication throws unhandled exception.', async () => { @@ -174,5 +174,34 @@ describe('SAML authentication routes', () => { headers: { location: 'http://redirect-to/path' }, }); }); + + it('passes `RelayState` within login attempt.', async () => { + authc.login.mockResolvedValue(AuthenticationResult.redirectTo('http://redirect-to/path')); + + const redirectResponse = Symbol('error'); + const responseFactory = httpServerMock.createResponseFactory(); + responseFactory.redirected.mockReturnValue(redirectResponse as any); + + const request = httpServerMock.createKibanaRequest({ + body: { SAMLResponse: 'saml-response', RelayState: '/app/kibana' }, + }); + + await expect(routeHandler({} as any, request, responseFactory)).resolves.toBe( + redirectResponse + ); + + expect(authc.login).toHaveBeenCalledWith(request, { + provider: { type: 'saml' }, + value: { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: 'saml-response', + relayState: '/app/kibana', + }, + }); + + expect(responseFactory.redirected).toHaveBeenCalledWith({ + headers: { location: 'http://redirect-to/path' }, + }); + }); }); }); diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index 30e1f6f336bdd..ce7516c2c9d88 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -89,10 +89,10 @@ export function defineSAMLRoutes({ { path: '/api/security/saml/callback', validate: { - body: schema.object({ - SAMLResponse: schema.string(), - RelayState: schema.maybe(schema.string()), - }), + body: schema.object( + { SAMLResponse: schema.string(), RelayState: schema.maybe(schema.string()) }, + { unknowns: 'ignore' } + ), }, options: { authRequired: false, xsrfRequired: false }, }, @@ -101,7 +101,11 @@ export function defineSAMLRoutes({ // When authenticating using SAML we _expect_ to redirect to the Kibana target location. const authenticationResult = await authc.login(request, { provider: { type: SAMLAuthenticationProvider.type }, - value: { type: SAMLLogin.LoginWithSAMLResponse, samlResponse: request.body.SAMLResponse }, + value: { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: request.body.SAMLResponse, + relayState: request.body.RelayState, + }, }); if (authenticationResult.redirected()) { diff --git a/x-pack/test/login_selector_api_integration/apis/login_selector.ts b/x-pack/test/login_selector_api_integration/apis/login_selector.ts index 57c91bd49808e..54b37fe52cc56 100644 --- a/x-pack/test/login_selector_api_integration/apis/login_selector.ts +++ b/x-pack/test/login_selector_api_integration/apis/login_selector.ts @@ -102,7 +102,7 @@ export default function ({ getService }: FtrProviderContext) { }); } - it('should be able to log in via IdP initiated login for any configured realm', async () => { + it('should be able to log in via IdP initiated login for any configured provider', async () => { for (const providerName of ['saml1', 'saml2']) { const authenticationResponse = await supertest .post('/api/security/saml/callback') @@ -124,6 +124,57 @@ export default function ({ getService }: FtrProviderContext) { } }); + it('should redirect to URL from relay state in case of IdP initiated login only for providers that explicitly enabled that behaviour', async () => { + for (const { providerName, redirectURL } of [ + { providerName: 'saml1', redirectURL: '/' }, + { providerName: 'saml2', redirectURL: '/app/kibana#/dashboards' }, + ]) { + const authenticationResponse = await supertest + .post('/api/security/saml/callback') + .ca(CA_CERT) + .type('form') + .send({ + SAMLResponse: await createSAMLResponse({ + issuer: `http://www.elastic.co/${providerName}`, + }), + }) + .send({ RelayState: '/app/kibana#/dashboards' }) + .expect(302); + + // User should be redirected to the base URL. + expect(authenticationResponse.headers.location).to.be(redirectURL); + + const cookies = authenticationResponse.headers['set-cookie']; + expect(cookies).to.have.length(1); + + await checkSessionCookie(request.cookie(cookies[0])!, 'a@b.c', providerName); + } + }); + + it('should not redirect to URL from relay state in case of IdP initiated login if URL is not internal', async () => { + for (const providerName of ['saml1', 'saml2']) { + const authenticationResponse = await supertest + .post('/api/security/saml/callback') + .ca(CA_CERT) + .type('form') + .send({ + SAMLResponse: await createSAMLResponse({ + issuer: `http://www.elastic.co/${providerName}`, + }), + }) + .send({ RelayState: 'http://www.elastic.co/app/kibana#/dashboards' }) + .expect(302); + + // User should be redirected to the base URL. + expect(authenticationResponse.headers.location).to.be('/'); + + const cookies = authenticationResponse.headers['set-cookie']; + expect(cookies).to.have.length(1); + + await checkSessionCookie(request.cookie(cookies[0])!, 'a@b.c', providerName); + } + }); + it('should be able to log in via IdP initiated login even if session with other provider type exists', async () => { const basicAuthenticationResponse = await supertest .post('/internal/security/login') @@ -193,6 +244,43 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie(saml2SessionCookie, 'a@b.c', 'saml2'); }); + it('should redirect to URL from relay state in case of IdP initiated login even if session with other SAML provider exists', async () => { + // First login with `saml1`. + const saml1AuthenticationResponse = await supertest + .post('/api/security/saml/callback') + .ca(CA_CERT) + .send({ + SAMLResponse: await createSAMLResponse({ issuer: `http://www.elastic.co/saml1` }), + }) + .expect(302); + + const saml1SessionCookie = request.cookie( + saml1AuthenticationResponse.headers['set-cookie'][0] + )!; + await checkSessionCookie(saml1SessionCookie, 'a@b.c', 'saml1'); + + // And now try to login with `saml2`. + const saml2AuthenticationResponse = await supertest + .post('/api/security/saml/callback') + .ca(CA_CERT) + .set('Cookie', saml1SessionCookie.cookieString()) + .type('form') + .send({ + SAMLResponse: await createSAMLResponse({ issuer: `http://www.elastic.co/saml2` }), + }) + .send({ RelayState: '/app/kibana#/dashboards' }) + .expect(302); + + // It should be `/overwritten_session` with `?next='/app/kibana#/dashboards'` instead of just + // `'/app/kibana#/dashboards'` once it's generalized. + expect(saml2AuthenticationResponse.headers.location).to.be('/app/kibana#/dashboards'); + + const saml2SessionCookie = request.cookie( + saml2AuthenticationResponse.headers['set-cookie'][0] + )!; + await checkSessionCookie(saml2SessionCookie, 'a@b.c', 'saml2'); + }); + // Ideally we should be able to abandon intermediate session and let user log in, but for the // time being we cannot distinguish errors coming from Elasticsearch for the case when SAML // response just doesn't correspond to request ID we have in intermediate cookie and the case diff --git a/x-pack/test/login_selector_api_integration/config.ts b/x-pack/test/login_selector_api_integration/config.ts index ba7aadb121e82..67bc2e6f17b56 100644 --- a/x-pack/test/login_selector_api_integration/config.ts +++ b/x-pack/test/login_selector_api_integration/config.ts @@ -127,7 +127,12 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { oidc: { oidc1: { order: 3, realm: 'oidc1' } }, saml: { saml1: { order: 1, realm: 'saml1' }, - saml2: { order: 5, realm: 'saml2', maxRedirectURLSize: '100b' }, + saml2: { + order: 5, + realm: 'saml2', + maxRedirectURLSize: '100b', + useRelayStateDeepLink: true, + }, }, })}`, ], From b270321ff334afec7794e0ba0bbcbdbb6d82ab07 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Wed, 24 Jun 2020 09:20:38 +0100 Subject: [PATCH 29/36] [ML] Fixes anomaly chart and validation for one week bucket span (#69671) * [ML] Fixes anomaly chart and validation for one week bucket span * [ML] Fix interval Jest tests --- x-pack/plugins/ml/common/util/job_utils.ts | 34 +++++++++++++++---- .../ml/common/util/parse_interval.test.ts | 16 +++++++-- .../plugins/ml/common/util/parse_interval.ts | 19 +++++++++-- .../new_job/common/job_creator/job_creator.ts | 2 +- .../job_creator/single_metric_job_creator.ts | 2 +- .../jobs/new_job/common/job_validator/util.ts | 12 ++++--- .../public/application/util/time_buckets.js | 12 +++++-- .../application/util/time_buckets.test.js | 30 ++++++++-------- .../job_validation/job_validation.test.ts | 4 +-- .../job_validation/validate_bucket_span.js | 10 +++--- .../job_validation/validate_time_range.ts | 2 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 13 files changed, 99 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 1fef0e6e2ecba..7ea4ceccf578d 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -6,6 +6,7 @@ import _ from 'lodash'; import semver from 'semver'; +import { Duration } from 'moment'; // @ts-ignore import numeral from '@elastic/numeral'; @@ -433,7 +434,7 @@ export function basicJobValidation( messages.push({ id: 'bucket_span_empty' }); valid = false; } else { - if (isValidTimeFormat(job.analysis_config.bucket_span)) { + if (isValidTimeInterval(job.analysis_config.bucket_span)) { messages.push({ id: 'bucket_span_valid', bucketSpan: job.analysis_config.bucket_span, @@ -490,14 +491,14 @@ export function basicDatafeedValidation(datafeed: Datafeed): ValidationResults { if (datafeed) { let queryDelayMessage = { id: 'query_delay_valid' }; - if (isValidTimeFormat(datafeed.query_delay) === false) { + if (isValidTimeInterval(datafeed.query_delay) === false) { queryDelayMessage = { id: 'query_delay_invalid' }; valid = false; } messages.push(queryDelayMessage); let frequencyMessage = { id: 'frequency_valid' }; - if (isValidTimeFormat(datafeed.frequency) === false) { + if (isValidTimeInterval(datafeed.frequency) === false) { frequencyMessage = { id: 'frequency_invalid' }; valid = false; } @@ -591,12 +592,33 @@ export function validateGroupNames(job: Job): ValidationResults { }; } -function isValidTimeFormat(value: string | undefined): boolean { +/** + * Parses the supplied string to a time interval suitable for use in an ML anomaly + * detection job or datafeed. + * @param value the string to parse + * @return {Duration} the parsed interval, or null if it does not represent a valid + * time interval. + */ +export function parseTimeIntervalForJob(value: string | undefined): Duration | null { + if (value === undefined) { + return null; + } + + // Must be a valid interval, greater than zero, + // and if specified in ms must be a multiple of 1000ms. + const interval = parseInterval(value, true); + return interval !== null && interval.asMilliseconds() !== 0 && interval.milliseconds() === 0 + ? interval + : null; +} + +// Checks that the value for a field which represents a time interval, +// such as a job bucket span or datafeed query delay, is valid. +function isValidTimeInterval(value: string | undefined): boolean { if (value === undefined) { return true; } - const interval = parseInterval(value); - return interval !== null && interval.asMilliseconds() !== 0; + return parseTimeIntervalForJob(value) !== null; } // Returns the latest of the last source data and last processed bucket timestamp, diff --git a/x-pack/plugins/ml/common/util/parse_interval.test.ts b/x-pack/plugins/ml/common/util/parse_interval.test.ts index 1717b2f0dd80b..be7ca2d55eecf 100644 --- a/x-pack/plugins/ml/common/util/parse_interval.test.ts +++ b/x-pack/plugins/ml/common/util/parse_interval.test.ts @@ -7,7 +7,7 @@ import { parseInterval } from './parse_interval'; describe('ML parse interval util', () => { - test('correctly parses an interval containing unit and value', () => { + test('should correctly parse an interval containing a valid unit and value', () => { expect(parseInterval('1d')!.as('d')).toBe(1); expect(parseInterval('2y')!.as('y')).toBe(2); expect(parseInterval('5M')!.as('M')).toBe(5); @@ -20,15 +20,25 @@ describe('ML parse interval util', () => { expect(parseInterval('0s')!.as('h')).toBe(0); }); - test('correctly handles zero value intervals', () => { + test('should correctly handle zero value intervals', () => { expect(parseInterval('0h')!.as('h')).toBe(0); expect(parseInterval('0d')).toBe(null); }); - test('returns null for an invalid interval', () => { + test('should return null for an invalid interval', () => { expect(parseInterval('')).toBe(null); expect(parseInterval('234asdf')).toBe(null); expect(parseInterval('m')).toBe(null); expect(parseInterval('1.5h')).toBe(null); }); + + test('should correctly check for whether the interval units are valid Elasticsearch time units', () => { + expect(parseInterval('100s', true)!.as('s')).toBe(100); + expect(parseInterval('5m', true)!.as('m')).toBe(5); + expect(parseInterval('24h', true)!.as('h')).toBe(24); + expect(parseInterval('7d', true)!.as('d')).toBe(7); + expect(parseInterval('1w', true)).toBe(null); + expect(parseInterval('1M', true)).toBe(null); + expect(parseInterval('1y', true)).toBe(null); + }); }); diff --git a/x-pack/plugins/ml/common/util/parse_interval.ts b/x-pack/plugins/ml/common/util/parse_interval.ts index 0f348f43d47b3..da6cd9db67792 100644 --- a/x-pack/plugins/ml/common/util/parse_interval.ts +++ b/x-pack/plugins/ml/common/util/parse_interval.ts @@ -16,7 +16,15 @@ const INTERVAL_STRING_RE = new RegExp('^([0-9]*)\\s*(' + dateMath.units.join('|' // for units of hour or less. const SUPPORT_ZERO_DURATION_UNITS: SupportedUnits[] = ['ms', 's', 'm', 'h']; +// List of time units which are supported for use in Elasticsearch durations +// (such as anomaly detection job bucket spans) +// See https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units +const SUPPORT_ES_DURATION_UNITS: SupportedUnits[] = ['ms', 's', 'm', 'h', 'd']; + // Parses an interval String, such as 7d, 1h or 30m to a moment duration. +// Optionally carries out an additional check that the interval is supported as a +// time unit by Elasticsearch, as units greater than 'd' for example cannot be used +// for anomaly detection job bucket spans. // Differs from the Kibana ui/utils/parse_interval in the following ways: // 1. A value-less interval such as 'm' is not allowed - in line with the ML back-end // not accepting such interval Strings for the bucket span of a job. @@ -25,7 +33,7 @@ const SUPPORT_ZERO_DURATION_UNITS: SupportedUnits[] = ['ms', 's', 'm', 'h']; // to work with units less than 'day'. // 3. Fractional intervals e.g. 1.5h or 4.5d are not allowed, in line with the behaviour // of the Elasticsearch date histogram aggregation. -export function parseInterval(interval: string): Duration | null { +export function parseInterval(interval: string, checkValidEsUnit = false): Duration | null { const matches = String(interval).trim().match(INTERVAL_STRING_RE); if (!Array.isArray(matches) || matches.length < 3) { return null; @@ -36,8 +44,13 @@ export function parseInterval(interval: string): Duration | null { const unit = matches[2] as SupportedUnits; // In line with moment.js, only allow zero value intervals when the unit is less than 'day'. - // And check for isNaN as e.g. valueless 'm' will pass the regex test. - if (isNaN(value) || (value < 1 && SUPPORT_ZERO_DURATION_UNITS.indexOf(unit) === -1)) { + // And check for isNaN as e.g. valueless 'm' will pass the regex test, + // plus an optional check that the unit is not w/M/y which are not fully supported by ES. + if ( + isNaN(value) || + (value < 1 && SUPPORT_ZERO_DURATION_UNITS.indexOf(unit) === -1) || + (checkValidEsUnit === true && SUPPORT_ES_DURATION_UNITS.indexOf(unit) === -1) + ) { return null; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index 89a0c45828737..d8c4dab150fb5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -155,7 +155,7 @@ export class JobCreator { } protected _setBucketSpanMs(bucketSpan: BucketSpan) { - const bs = parseInterval(bucketSpan); + const bs = parseInterval(bucketSpan, true); this._bucketSpanMs = bs === null ? 0 : bs.asMilliseconds(); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts index febfc5ca3eb9e..e884da5470cc5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts @@ -76,7 +76,7 @@ export class SingleMetricJobCreator extends JobCreator { const functionName = this._aggs[0].dslName; const timeField = this._job_config.data_description.time_field; - const duration = parseInterval(this._job_config.analysis_config.bucket_span); + const duration = parseInterval(this._job_config.analysis_config.bucket_span, true); if (duration === null) { return; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts index d5cc1cf535a78..b97841542f76a 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts @@ -142,7 +142,7 @@ export function populateValidationMessages( basicValidations.bucketSpan.message = msg; } else if (validationResults.contains('bucket_span_invalid')) { basicValidations.bucketSpan.valid = false; - basicValidations.bucketSpan.message = invalidTimeFormatMessage( + basicValidations.bucketSpan.message = invalidTimeIntervalMessage( jobConfig.analysis_config.bucket_span ); } @@ -163,12 +163,12 @@ export function populateValidationMessages( if (validationResults.contains('query_delay_invalid')) { basicValidations.queryDelay.valid = false; - basicValidations.queryDelay.message = invalidTimeFormatMessage(datafeedConfig.query_delay); + basicValidations.queryDelay.message = invalidTimeIntervalMessage(datafeedConfig.query_delay); } if (validationResults.contains('frequency_invalid')) { basicValidations.frequency.valid = false; - basicValidations.frequency.message = invalidTimeFormatMessage(datafeedConfig.frequency); + basicValidations.frequency.message = invalidTimeIntervalMessage(datafeedConfig.frequency); } } @@ -202,16 +202,18 @@ export function checkForExistingJobAndGroupIds( }; } -function invalidTimeFormatMessage(value: string | undefined) { +function invalidTimeIntervalMessage(value: string | undefined) { return i18n.translate( 'xpack.ml.newJob.wizard.validateJob.frequencyInvalidTimeIntervalFormatErrorMessage', { defaultMessage: - '{value} is not a valid time interval format e.g. {tenMinutes}, {oneHour}. It also needs to be higher than zero.', + '{value} is not a valid time interval format e.g. {thirtySeconds}, {tenMinutes}, {oneHour}, {sevenDays}. It also needs to be higher than zero.', values: { value, + thirtySeconds: '30s', tenMinutes: '10m', oneHour: '1h', + sevenDays: '7d', }, } ); diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.js b/x-pack/plugins/ml/public/application/util/time_buckets.js index 1915a4ce6516b..19d499faf6c8d 100644 --- a/x-pack/plugins/ml/public/application/util/time_buckets.js +++ b/x-pack/plugins/ml/public/application/util/time_buckets.js @@ -14,7 +14,11 @@ import { getFieldFormats, getUiSettings } from './dependency_cache'; import { FIELD_FORMAT_IDS, UI_SETTINGS } from '../../../../../../src/plugins/data/public'; const unitsDesc = dateMath.unitsDesc; -const largeMax = unitsDesc.indexOf('w'); // Multiple units of week or longer converted to days for ES intervals. + +// Index of the list of time interval units at which larger units (i.e. weeks, months, years) need +// need to be converted to multiples of the largest unit supported in ES aggregation intervals (i.e. days). +// Note that similarly the largest interval supported for ML bucket spans is 'd'. +const timeUnitsMaxSupportedIndex = unitsDesc.indexOf('w'); const calcAuto = timeBucketsCalcAutoIntervalProvider(); @@ -383,9 +387,11 @@ export function calcEsInterval(duration) { const val = duration.as(unit); // find a unit that rounds neatly if (val >= 1 && Math.floor(val) === val) { - // if the unit is "large", like years, but isn't set to 1, ES will throw an error. + // Apart from for date histograms, ES only supports time units up to 'd', + // meaning we can't for example use 'w' for job bucket spans. + // See https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units // So keep going until we get out of the "large" units. - if (i <= largeMax && val !== 1) { + if (i <= timeUnitsMaxSupportedIndex) { continue; } diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.test.js b/x-pack/plugins/ml/public/application/util/time_buckets.test.js index 250c7255f5b99..6ebd518841bd1 100644 --- a/x-pack/plugins/ml/public/application/util/time_buckets.test.js +++ b/x-pack/plugins/ml/public/application/util/time_buckets.test.js @@ -232,14 +232,14 @@ describe('ML - time buckets', () => { expression: '3d', }); expect(calcEsInterval(moment.duration(7, 'd'))).toEqual({ - value: 1, - unit: 'w', - expression: '1w', + value: 7, + unit: 'd', + expression: '7d', }); expect(calcEsInterval(moment.duration(1, 'w'))).toEqual({ - value: 1, - unit: 'w', - expression: '1w', + value: 7, + unit: 'd', + expression: '7d', }); expect(calcEsInterval(moment.duration(4, 'w'))).toEqual({ value: 28, @@ -247,19 +247,19 @@ describe('ML - time buckets', () => { expression: '28d', }); expect(calcEsInterval(moment.duration(1, 'M'))).toEqual({ - value: 1, - unit: 'M', - expression: '1M', + value: 30, + unit: 'd', + expression: '30d', }); expect(calcEsInterval(moment.duration(12, 'M'))).toEqual({ - value: 1, - unit: 'y', - expression: '1y', + value: 365, + unit: 'd', + expression: '365d', }); expect(calcEsInterval(moment.duration(1, 'y'))).toEqual({ - value: 1, - unit: 'y', - expression: '1y', + value: 365, + unit: 'd', + expression: '365d', }); }); }); diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts index f9999a06f38ed..0aae4388e7399 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts @@ -133,11 +133,11 @@ describe('ML - validateJob', () => { }); }; it('invalid bucket span formats', () => { - const invalidBucketSpanFormats = ['a', '10', '$']; + const invalidBucketSpanFormats = ['a', '10', '$', '500ms', '1w', '2M', '1y']; return bucketSpanFormatTests(invalidBucketSpanFormats, 'bucket_span_invalid'); }); it('valid bucket span formats', () => { - const validBucketSpanFormats = ['1s', '4h', '10d', '6w', '2m', '3y']; + const validBucketSpanFormats = ['5000ms', '1s', '2m', '4h', '10d']; return bucketSpanFormatTests(validBucketSpanFormats, 'bucket_span_valid'); }); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js index 46d05d3cf7637..7dc2ad7ff3b8f 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js +++ b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js @@ -5,9 +5,8 @@ */ import { estimateBucketSpanFactory } from '../../models/bucket_span_estimator'; -import { mlFunctionToESAggregation } from '../../../common/util/job_utils'; +import { mlFunctionToESAggregation, parseTimeIntervalForJob } from '../../../common/util/job_utils'; import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../common/constants/validation'; -import { parseInterval } from '../../../common/util/parse_interval'; import { validateJobObject } from './validate_job_object'; @@ -65,8 +64,11 @@ export async function validateBucketSpan( } const messages = []; - const parsedBucketSpan = parseInterval(job.analysis_config.bucket_span); - if (parsedBucketSpan === null || parsedBucketSpan.asMilliseconds() === 0) { + + // Bucket span must be a valid interval, greater than 0, + // and if specified in ms must be a multiple of 1000ms + const parsedBucketSpan = parseTimeIntervalForJob(job.analysis_config.bucket_span); + if (parsedBucketSpan === null) { messages.push({ id: 'bucket_span_invalid' }); return messages; } diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts index be6c9a7157aeb..f60ca66b092f9 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts @@ -78,7 +78,7 @@ export async function validateTimeRange( } // check for minimum time range (25 buckets or 2 hours, whichever is longer) - const interval = parseInterval(job.analysis_config.bucket_span); + const interval = parseInterval(job.analysis_config.bucket_span, true); if (interval === null) { messages.push({ id: 'bucket_span_invalid' }); } else { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 76650bf421f22..14a58ec595abc 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10691,7 +10691,6 @@ "xpack.ml.newJob.wizard.timeRangeStep.timeRangePicker.startDateLabel": "開始日", "xpack.ml.newJob.wizard.validateJob.bucketSpanMustBeSetErrorMessage": "バケットスパンを設定する必要があります", "xpack.ml.newJob.wizard.validateJob.duplicatedDetectorsErrorMessage": "重複する検知器が検出されました。", - "xpack.ml.newJob.wizard.validateJob.frequencyInvalidTimeIntervalFormatErrorMessage": "{value} は有効な時間間隔のフォーマット (例: {tenMinutes}、{oneHour}) ではありません。また、0 よりも大きい数字である必要があります。", "xpack.ml.newJob.wizard.validateJob.groupNameAlreadyExists": "グループ ID が既に存在します。グループ ID は既存のジョブやグループと同じにできません。", "xpack.ml.newJob.wizard.validateJob.jobGroupAllowedCharactersDescription": "ジョブグループ名にはアルファベットの小文字 (a-z と 0-9)、ハイフンまたはアンダーラインが使用でき、最初と最後を英数字にする必要があります", "xpack.ml.newJob.wizard.validateJob.jobGroupMaxLengthDescription": "ジョブグループ名は {maxLength, plural, one {# 文字} other {# 文字}} 以内でなければなりません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index dc20275561cb0..9c58aeba1dbaa 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10695,7 +10695,6 @@ "xpack.ml.newJob.wizard.timeRangeStep.timeRangePicker.startDateLabel": "开始日期", "xpack.ml.newJob.wizard.validateJob.bucketSpanMustBeSetErrorMessage": "必须设置存储桶跨度", "xpack.ml.newJob.wizard.validateJob.duplicatedDetectorsErrorMessage": "找到重复的检测工具。", - "xpack.ml.newJob.wizard.validateJob.frequencyInvalidTimeIntervalFormatErrorMessage": "{value} 不是有效的时间间隔格式,例如,{tenMinutes}、{oneHour}。还需要大于零。", "xpack.ml.newJob.wizard.validateJob.groupNameAlreadyExists": "组 ID 已存在。组 ID 不能与现有作业或组相同。", "xpack.ml.newJob.wizard.validateJob.jobGroupAllowedCharactersDescription": "作业组名称可以包含小写字母数字(a-z 和 0-9)、连字符或下划线;必须以字母数字字符开头和结尾", "xpack.ml.newJob.wizard.validateJob.jobGroupMaxLengthDescription": "作业组名称的长度不得超过 {maxLength, plural, one {# 个字符} other {# 个字符}}。", From 36b66a802e682ab01dca51029df1348f485b9b8b Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Wed, 24 Jun 2020 10:21:27 +0200 Subject: [PATCH 30/36] Migrate legacy import/export endpoints (#69474) * migrate legacy export routes to `legacy_export` plugin * adapt unit tests * remove already dead (already moved) libs --- src/legacy/core_plugins/kibana/index.js | 5 - .../server/lib/__tests__/relationships.js | 645 ------------------ .../lib/import/import_dashboards.test.js | 87 --- .../inject_meta_attributes.test.js | 148 ---- .../management/saved_objects/relationships.js | 64 -- .../kibana/server/routes/api/export/index.js | 52 -- src/plugins/legacy_export/kibana.json | 6 + .../legacy_export/server/index.ts} | 16 +- .../export/collect_references_deep.test.ts | 2 +- .../lib/export/collect_references_deep.ts | 2 +- .../server/lib/export/export_dashboards.ts} | 14 +- .../lib/import/import_dashboards.test.ts | 92 +++ .../server/lib/import/import_dashboards.ts} | 16 +- src/plugins/legacy_export/server/lib/index.ts | 21 + .../legacy_export/server/plugin.ts} | 43 +- .../legacy_export/server/routes/export.ts | 56 ++ .../legacy_export/server/routes/import.ts | 57 ++ .../legacy_export/server/routes/index.ts | 27 + 18 files changed, 298 insertions(+), 1055 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js delete mode 100644 src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.test.js delete mode 100644 src/legacy/core_plugins/kibana/server/lib/management/saved_objects/inject_meta_attributes.test.js delete mode 100644 src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js delete mode 100644 src/legacy/core_plugins/kibana/server/routes/api/export/index.js create mode 100644 src/plugins/legacy_export/kibana.json rename src/{legacy/core_plugins/kibana/server/lib/management/saved_objects/inject_meta_attributes.js => plugins/legacy_export/server/index.ts} (61%) rename src/{legacy/core_plugins/kibana => plugins/legacy_export}/server/lib/export/collect_references_deep.test.ts (98%) rename src/{legacy/core_plugins/kibana => plugins/legacy_export}/server/lib/export/collect_references_deep.ts (98%) rename src/{legacy/core_plugins/kibana/server/lib/export/export_dashboards.js => plugins/legacy_export/server/lib/export/export_dashboards.ts} (80%) create mode 100644 src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts rename src/{legacy/core_plugins/kibana/server/lib/import/import_dashboards.js => plugins/legacy_export/server/lib/import/import_dashboards.ts} (81%) create mode 100644 src/plugins/legacy_export/server/lib/index.ts rename src/{legacy/core_plugins/kibana/server/routes/api/import/index.js => plugins/legacy_export/server/plugin.ts} (55%) create mode 100644 src/plugins/legacy_export/server/routes/export.ts create mode 100644 src/plugins/legacy_export/server/routes/import.ts create mode 100644 src/plugins/legacy_export/server/routes/index.ts diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index ae613e0e80904..c70a1135a2c41 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -21,8 +21,6 @@ import Fs from 'fs'; import { resolve } from 'path'; import { promisify } from 'util'; -import { importApi } from './server/routes/api/import'; -import { exportApi } from './server/routes/api/export'; import { getUiSettingDefaults } from './server/ui_setting_defaults'; import { registerCspCollector } from './server/lib/csp_usage_collector'; import { injectVars } from './inject_vars'; @@ -91,9 +89,6 @@ export default function (kibana) { init: async function (server) { const { usageCollection } = server.newPlatform.setup.plugins; - // routes - importApi(server); - exportApi(server); registerCspCollector(usageCollection, server); server.injectUiAppVars('kibana', () => injectVars(server)); }, diff --git a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js deleted file mode 100644 index 4df0e7a140205..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js +++ /dev/null @@ -1,645 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { findRelationships } from '../management/saved_objects/relationships'; - -function getManagementaMock(savedObjectSchemas) { - return { - isImportAndExportable(type) { - return ( - !savedObjectSchemas[type] || savedObjectSchemas[type].isImportableAndExportable !== false - ); - }, - getDefaultSearchField(type) { - return savedObjectSchemas[type] && savedObjectSchemas[type].defaultSearchField; - }, - getIcon(type) { - return savedObjectSchemas[type] && savedObjectSchemas[type].icon; - }, - getTitle(savedObject) { - const { type } = savedObject; - const getTitle = savedObjectSchemas[type] && savedObjectSchemas[type].getTitle; - if (getTitle) { - return getTitle(savedObject); - } - }, - getEditUrl(savedObject) { - const { type } = savedObject; - const getEditUrl = savedObjectSchemas[type] && savedObjectSchemas[type].getEditUrl; - if (getEditUrl) { - return getEditUrl(savedObject); - } - }, - getInAppUrl(savedObject) { - const { type } = savedObject; - const getInAppUrl = savedObjectSchemas[type] && savedObjectSchemas[type].getInAppUrl; - if (getInAppUrl) { - return getInAppUrl(savedObject); - } - }, - }; -} - -const savedObjectsManagement = getManagementaMock({ - 'index-pattern': { - icon: 'indexPatternApp', - defaultSearchField: 'title', - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'management.kibana.index_patterns', - }; - }, - }, - visualization: { - icon: 'visualizeApp', - defaultSearchField: 'title', - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/objects/savedVisualizations/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/visualize#/edit/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'visualize.show', - }; - }, - }, - search: { - icon: 'search', - defaultSearchField: 'title', - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/objects/savedSearches/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/discover#//${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'discover.show', - }; - }, - }, - dashboard: { - icon: 'dashboardApp', - defaultSearchField: 'title', - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/objects/savedDashboards/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/kibana#/dashboard/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'dashboard.show', - }; - }, - }, -}); - -describe('findRelationships', () => { - it('should find relationships for dashboards', async () => { - const type = 'dashboard'; - const id = 'foo'; - const size = 10; - - const savedObjectsClient = { - get: () => ({ - attributes: { - panelsJSON: JSON.stringify([ - { panelRefName: 'panel_0' }, - { panelRefName: 'panel_1' }, - { panelRefName: 'panel_2' }, - ]), - }, - references: [ - { - name: 'panel_0', - type: 'visualization', - id: '1', - }, - { - name: 'panel_1', - type: 'visualization', - id: '2', - }, - { - name: 'panel_2', - type: 'visualization', - id: '3', - }, - ], - }), - bulkGet: () => ({ saved_objects: [] }), - find: () => ({ - saved_objects: [ - { - id: '1', - type: 'visualization', - attributes: { - title: 'Foo', - }, - }, - { - id: '2', - type: 'visualization', - attributes: { - title: 'Bar', - }, - }, - { - id: '3', - type: 'visualization', - attributes: { - title: 'FooBar', - }, - }, - ], - }), - }; - const result = await findRelationships(type, id, { - size, - savedObjectsClient, - savedObjectsManagement, - savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], - }); - expect(result).to.eql([ - { - id: '1', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'Foo', - editUrl: '/management/kibana/objects/savedVisualizations/1', - inAppUrl: { - path: '/app/visualize#/edit/1', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - { - id: '2', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'Bar', - editUrl: '/management/kibana/objects/savedVisualizations/2', - inAppUrl: { - path: '/app/visualize#/edit/2', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - { - id: '3', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'FooBar', - editUrl: '/management/kibana/objects/savedVisualizations/3', - inAppUrl: { - path: '/app/visualize#/edit/3', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - ]); - }); - - it('should find relationships for visualizations', async () => { - const type = 'visualization'; - const id = 'foo'; - const size = 10; - - const savedObjectsClient = { - get: () => ({ - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', - }), - }, - }, - references: [ - { - name: 'kibanaSavedObjectMeta.searchSourceJSON.index', - type: 'index-pattern', - id: '1', - }, - ], - }), - bulkGet: () => ({ - saved_objects: [ - { - id: '1', - type: 'index-pattern', - attributes: { - title: 'My Index Pattern', - }, - }, - ], - }), - find: () => ({ - saved_objects: [ - { - id: '1', - type: 'dashboard', - attributes: { - title: 'My Dashboard', - panelsJSON: JSON.stringify([ - { - type: 'visualization', - id, - }, - { - type: 'visualization', - id: 'foobar', - }, - ]), - }, - }, - { - id: '2', - type: 'dashboard', - attributes: { - title: 'Your Dashboard', - panelsJSON: JSON.stringify([ - { - type: 'visualization', - id, - }, - { - type: 'visualization', - id: 'foobar', - }, - ]), - }, - }, - ], - }), - }; - - const result = await findRelationships(type, id, { - size, - savedObjectsClient, - savedObjectsManagement, - savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], - }); - expect(result).to.eql([ - { - id: '1', - type: 'index-pattern', - relationship: 'child', - meta: { - icon: 'indexPatternApp', - title: 'My Index Pattern', - editUrl: '/management/kibana/indexPatterns/patterns/1', - inAppUrl: { - path: '/app/management/kibana/indexPatterns/patterns/1', - uiCapabilitiesPath: 'management.kibana.index_patterns', - }, - }, - }, - { - id: '1', - type: 'dashboard', - relationship: 'parent', - meta: { - icon: 'dashboardApp', - title: 'My Dashboard', - editUrl: '/management/kibana/objects/savedDashboards/1', - inAppUrl: { - path: '/app/kibana#/dashboard/1', - uiCapabilitiesPath: 'dashboard.show', - }, - }, - }, - { - id: '2', - type: 'dashboard', - relationship: 'parent', - meta: { - icon: 'dashboardApp', - title: 'Your Dashboard', - editUrl: '/management/kibana/objects/savedDashboards/2', - inAppUrl: { - path: '/app/kibana#/dashboard/2', - uiCapabilitiesPath: 'dashboard.show', - }, - }, - }, - ]); - }); - - it('should find relationships for saved searches', async () => { - const type = 'search'; - const id = 'foo'; - const size = 10; - - const savedObjectsClient = { - get: () => ({ - id: '1', - type: 'search', - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', - }), - }, - }, - references: [ - { - name: 'kibanaSavedObjectMeta.searchSourceJSON.index', - type: 'index-pattern', - id: '1', - }, - ], - }), - bulkGet: () => ({ - saved_objects: [ - { - id: '1', - type: 'index-pattern', - attributes: { - title: 'My Index Pattern', - }, - }, - ], - }), - find: () => ({ - saved_objects: [ - { - id: '1', - type: 'visualization', - attributes: { - title: 'Foo', - }, - }, - { - id: '2', - type: 'visualization', - attributes: { - title: 'Bar', - }, - }, - { - id: '3', - type: 'visualization', - attributes: { - title: 'FooBar', - }, - }, - ], - }), - }; - - const result = await findRelationships(type, id, { - size, - savedObjectsClient, - savedObjectsManagement, - savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], - }); - expect(result).to.eql([ - { - id: '1', - type: 'index-pattern', - relationship: 'child', - meta: { - icon: 'indexPatternApp', - title: 'My Index Pattern', - editUrl: '/management/kibana/indexPatterns/patterns/1', - inAppUrl: { - path: '/app/management/kibana/indexPatterns/patterns/1', - uiCapabilitiesPath: 'management.kibana.index_patterns', - }, - }, - }, - { - id: '1', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'Foo', - editUrl: '/management/kibana/objects/savedVisualizations/1', - inAppUrl: { - path: '/app/visualize#/edit/1', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - { - id: '2', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'Bar', - editUrl: '/management/kibana/objects/savedVisualizations/2', - inAppUrl: { - path: '/app/visualize#/edit/2', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - { - id: '3', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'FooBar', - editUrl: '/management/kibana/objects/savedVisualizations/3', - inAppUrl: { - path: '/app/visualize#/edit/3', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - ]); - }); - - it('should find relationships for index patterns', async () => { - const type = 'index-pattern'; - const id = 'foo'; - const size = 10; - - const savedObjectsClient = { - get: () => ({ - id: '1', - type: 'index-pattern', - attributes: { - title: 'My Index Pattern', - }, - }), - find: () => ({ - saved_objects: [ - { - id: '1', - type: 'visualization', - attributes: { - title: 'Foo', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo', - }), - }, - }, - }, - { - id: '2', - type: 'visualization', - attributes: { - title: 'Bar', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo', - }), - }, - }, - }, - { - id: '3', - type: 'visualization', - attributes: { - title: 'FooBar', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo2', - }), - }, - }, - }, - { - id: '1', - type: 'search', - attributes: { - title: 'My Saved Search', - }, - }, - ], - }), - }; - - const result = await findRelationships(type, id, { - size, - savedObjectsClient, - savedObjectsManagement, - savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], - }); - expect(result).to.eql([ - { - id: '1', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'Foo', - editUrl: '/management/kibana/objects/savedVisualizations/1', - inAppUrl: { - path: '/app/visualize#/edit/1', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - { - id: '2', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'Bar', - editUrl: '/management/kibana/objects/savedVisualizations/2', - inAppUrl: { - path: '/app/visualize#/edit/2', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - { - id: '3', - type: 'visualization', - relationship: 'parent', - meta: { - icon: 'visualizeApp', - title: 'FooBar', - editUrl: '/management/kibana/objects/savedVisualizations/3', - inAppUrl: { - path: '/app/visualize#/edit/3', - uiCapabilitiesPath: 'visualize.show', - }, - }, - }, - { - id: '1', - type: 'search', - relationship: 'parent', - meta: { - icon: 'search', - title: 'My Saved Search', - editUrl: '/management/kibana/objects/savedSearches/1', - inAppUrl: { - path: '/app/discover#//1', - uiCapabilitiesPath: 'discover.show', - }, - }, - }, - ]); - }); - - it('should return an empty object for non related objects', async () => { - const type = 'invalid'; - const id = 'foo'; - const size = 10; - - const savedObjectsClient = { - get: () => ({ - id: '1', - type: 'index-pattern', - attributes: { - title: 'My Index Pattern', - }, - references: [], - }), - find: () => ({ saved_objects: [] }), - }; - - const result = await findRelationships(type, id, { - size, - savedObjectsClient, - savedObjectsManagement, - savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], - }); - expect(result).to.eql({}); - }); -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.test.js b/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.test.js deleted file mode 100644 index 13e04e1e9e16e..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.test.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { importDashboards } from './import_dashboards'; -import sinon from 'sinon'; - -describe('importDashboards(req)', () => { - let req; - let bulkCreateStub; - beforeEach(() => { - bulkCreateStub = sinon.stub().returns(Promise.resolve({ saved_objects: [] })); - req = { - query: {}, - payload: { - version: '6.0.0', - objects: [ - { id: 'dashboard-01', type: 'dashboard', attributes: { panelJSON: '{}' } }, - { id: 'panel-01', type: 'visualization', attributes: { visState: '{}' } }, - ], - }, - getSavedObjectsClient() { - return { - bulkCreate: bulkCreateStub, - }; - }, - }; - }); - - test('should call bulkCreate with each asset', () => { - return importDashboards(req).then(() => { - expect(bulkCreateStub.calledOnce).toEqual(true); - expect(bulkCreateStub.args[0][0]).toEqual([ - { - id: 'dashboard-01', - type: 'dashboard', - attributes: { panelJSON: '{}' }, - migrationVersion: {}, - }, - { - id: 'panel-01', - type: 'visualization', - attributes: { visState: '{}' }, - migrationVersion: {}, - }, - ]); - }); - }); - - test('should call bulkCreate with overwrite true if force is truthy', () => { - req.query = { force: 'true' }; - return importDashboards(req).then(() => { - expect(bulkCreateStub.calledOnce).toEqual(true); - expect(bulkCreateStub.args[0][1]).toEqual({ overwrite: true }); - }); - }); - - test('should exclude types based on exclude argument', () => { - req.query = { exclude: 'visualization' }; - return importDashboards(req).then(() => { - expect(bulkCreateStub.calledOnce).toEqual(true); - expect(bulkCreateStub.args[0][0]).toEqual([ - { - id: 'dashboard-01', - type: 'dashboard', - attributes: { panelJSON: '{}' }, - migrationVersion: {}, - }, - ]); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/inject_meta_attributes.test.js b/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/inject_meta_attributes.test.js deleted file mode 100644 index b98160346011a..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/inject_meta_attributes.test.js +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { injectMetaAttributes } from './inject_meta_attributes'; - -function getManagementMock(savedObjectSchemas) { - return { - isImportAndExportable(type) { - return ( - !savedObjectSchemas[type] || savedObjectSchemas[type].isImportableAndExportable !== false - ); - }, - getDefaultSearchField(type) { - return savedObjectSchemas[type] && savedObjectSchemas[type].defaultSearchField; - }, - getIcon(type) { - return savedObjectSchemas[type] && savedObjectSchemas[type].icon; - }, - getTitle(savedObject) { - const { type } = savedObject; - const getTitle = savedObjectSchemas[type] && savedObjectSchemas[type].getTitle; - if (getTitle) { - return getTitle(savedObject); - } - }, - getEditUrl(savedObject) { - const { type } = savedObject; - const getEditUrl = savedObjectSchemas[type] && savedObjectSchemas[type].getEditUrl; - if (getEditUrl) { - return getEditUrl(savedObject); - } - }, - getInAppUrl(savedObject) { - const { type } = savedObject; - const getInAppUrl = savedObjectSchemas[type] && savedObjectSchemas[type].getInAppUrl; - if (getInAppUrl) { - return getInAppUrl(savedObject); - } - }, - }; -} - -test('works when no schema is defined for the type', () => { - const savedObject = { type: 'a' }; - const savedObjectsManagement = getManagementMock({}); - const result = injectMetaAttributes(savedObject, savedObjectsManagement); - expect(result).toEqual({ type: 'a', meta: {} }); -}); - -test('inject icon into meta attribute', () => { - const savedObject = { - type: 'a', - }; - const savedObjectsManagement = getManagementMock({ - a: { - icon: 'my-icon', - }, - }); - const result = injectMetaAttributes(savedObject, savedObjectsManagement); - expect(result).toEqual({ - type: 'a', - meta: { - icon: 'my-icon', - }, - }); -}); - -test('injects title into meta attribute', () => { - const savedObject = { - type: 'a', - }; - const savedObjectsManagement = getManagementMock({ - a: { - getTitle() { - return 'my-title'; - }, - }, - }); - const result = injectMetaAttributes(savedObject, savedObjectsManagement); - expect(result).toEqual({ - type: 'a', - meta: { - title: 'my-title', - }, - }); -}); - -test('injects editUrl into meta attribute', () => { - const savedObject = { - type: 'a', - }; - const savedObjectsManagement = getManagementMock({ - a: { - getEditUrl() { - return 'my-edit-url'; - }, - }, - }); - const result = injectMetaAttributes(savedObject, savedObjectsManagement); - expect(result).toEqual({ - type: 'a', - meta: { - editUrl: 'my-edit-url', - }, - }); -}); - -test('injects inAppUrl meta attribute', () => { - const savedObject = { - type: 'a', - }; - const savedObjectsManagement = getManagementMock({ - a: { - getInAppUrl() { - return { - path: 'my-in-app-url', - uiCapabilitiesPath: 'ui.path', - }; - }, - }, - }); - const result = injectMetaAttributes(savedObject, savedObjectsManagement); - expect(result).toEqual({ - type: 'a', - meta: { - inAppUrl: { - path: 'my-in-app-url', - uiCapabilitiesPath: 'ui.path', - }, - }, - }); -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js b/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js deleted file mode 100644 index e0a6c574b7ad8..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { pick } from 'lodash'; -import { injectMetaAttributes } from './inject_meta_attributes'; - -export async function findRelationships(type, id, options = {}) { - const { size, savedObjectsClient, savedObjectTypes, savedObjectsManagement } = options; - - const { references = [] } = await savedObjectsClient.get(type, id); - - // Use a map to avoid duplicates, it does happen but have a different "name" in the reference - const referencedToBulkGetOpts = new Map( - references.map(({ type, id }) => [`${type}:${id}`, { id, type }]) - ); - - const [referencedObjects, referencedResponse] = await Promise.all([ - referencedToBulkGetOpts.size > 0 - ? savedObjectsClient.bulkGet([...referencedToBulkGetOpts.values()]) - : Promise.resolve({ saved_objects: [] }), - savedObjectsClient.find({ - hasReference: { type, id }, - perPage: size, - type: savedObjectTypes, - }), - ]); - - return [].concat( - referencedObjects.saved_objects - .map((obj) => injectMetaAttributes(obj, savedObjectsManagement)) - .map(extractCommonProperties) - .map((obj) => ({ - ...obj, - relationship: 'child', - })), - referencedResponse.saved_objects - .map((obj) => injectMetaAttributes(obj, savedObjectsManagement)) - .map(extractCommonProperties) - .map((obj) => ({ - ...obj, - relationship: 'parent', - })) - ); -} - -function extractCommonProperties(savedObject) { - return pick(savedObject, ['id', 'type', 'meta']); -} diff --git a/src/legacy/core_plugins/kibana/server/routes/api/export/index.js b/src/legacy/core_plugins/kibana/server/routes/api/export/index.js deleted file mode 100644 index ef556ed53f4fc..0000000000000 --- a/src/legacy/core_plugins/kibana/server/routes/api/export/index.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Joi from 'joi'; -import moment from 'moment'; - -import { exportDashboards } from '../../../lib/export/export_dashboards'; - -export function exportApi(server) { - server.route({ - path: '/api/kibana/dashboards/export', - config: { - validate: { - query: Joi.object().keys({ - dashboard: Joi.alternatives() - .try(Joi.string(), Joi.array().items(Joi.string())) - .required(), - }), - }, - tags: ['api'], - }, - method: ['GET'], - handler: async (req, h) => { - const currentDate = moment.utc(); - return exportDashboards(req).then((resp) => { - const json = JSON.stringify(resp, null, ' '); - const filename = `kibana-dashboards.${currentDate.format('YYYY-MM-DD-HH-mm-ss')}.json`; - return h - .response(json) - .header('Content-Disposition', `attachment; filename="${filename}"`) - .header('Content-Type', 'application/json') - .header('Content-Length', Buffer.byteLength(json, 'utf8')); - }); - }, - }); -} diff --git a/src/plugins/legacy_export/kibana.json b/src/plugins/legacy_export/kibana.json new file mode 100644 index 0000000000000..f382cab1e655d --- /dev/null +++ b/src/plugins/legacy_export/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "legacyExport", + "version": "kibana", + "server": true, + "ui": false +} diff --git a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/inject_meta_attributes.js b/src/plugins/legacy_export/server/index.ts similarity index 61% rename from src/legacy/core_plugins/kibana/server/lib/management/saved_objects/inject_meta_attributes.js rename to src/plugins/legacy_export/server/index.ts index 25f6341cf25da..52d4da168b71d 100644 --- a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/inject_meta_attributes.js +++ b/src/plugins/legacy_export/server/index.ts @@ -17,17 +17,7 @@ * under the License. */ -export function injectMetaAttributes(savedObject, savedObjectsManagement) { - const result = { - ...savedObject, - meta: savedObject.meta || {}, - }; +import { PluginInitializer } from 'src/core/server'; +import { LegacyExportPlugin } from './plugin'; - // Add extra meta information - result.meta.icon = savedObjectsManagement.getIcon(savedObject.type); - result.meta.title = savedObjectsManagement.getTitle(savedObject); - result.meta.editUrl = savedObjectsManagement.getEditUrl(savedObject); - result.meta.inAppUrl = savedObjectsManagement.getInAppUrl(savedObject); - - return result; -} +export const plugin: PluginInitializer<{}, {}> = (context) => new LegacyExportPlugin(context); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts b/src/plugins/legacy_export/server/lib/export/collect_references_deep.test.ts similarity index 98% rename from src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts rename to src/plugins/legacy_export/server/lib/export/collect_references_deep.test.ts index d1be3d64fdb3f..603afe364aba2 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts +++ b/src/plugins/legacy_export/server/lib/export/collect_references_deep.test.ts @@ -18,8 +18,8 @@ */ import { SavedObject, SavedObjectAttributes } from 'src/core/server'; +import { savedObjectsClientMock } from '../../../../../core/server/mocks'; import { collectReferencesDeep } from './collect_references_deep'; -import { savedObjectsClientMock } from '../../../../../../core/server/mocks'; const data: Array> = [ { diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts b/src/plugins/legacy_export/server/lib/export/collect_references_deep.ts similarity index 98% rename from src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts rename to src/plugins/legacy_export/server/lib/export/collect_references_deep.ts index e44db901a0cb8..8e8ae1332d74b 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts +++ b/src/plugins/legacy_export/server/lib/export/collect_references_deep.ts @@ -29,7 +29,7 @@ interface ObjectsToCollect { export async function collectReferencesDeep( savedObjectClient: SavedObjectsClientContract, objects: ObjectsToCollect[] -) { +): Promise { let result: SavedObject[] = []; const queue = [...objects]; while (queue.length !== 0) { diff --git a/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js b/src/plugins/legacy_export/server/lib/export/export_dashboards.ts similarity index 80% rename from src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js rename to src/plugins/legacy_export/server/lib/export/export_dashboards.ts index 913ebff588f84..4b2e548f3f7fd 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js +++ b/src/plugins/legacy_export/server/lib/export/export_dashboards.ts @@ -17,19 +17,19 @@ * under the License. */ -import _ from 'lodash'; +import { SavedObjectsClientContract } from 'src/core/server'; import { collectReferencesDeep } from './collect_references_deep'; -export async function exportDashboards(req) { - const ids = _.flatten([req.query.dashboard]); - const config = req.server.config(); - - const savedObjectsClient = req.getSavedObjectsClient(); +export async function exportDashboards( + ids: string[], + savedObjectsClient: SavedObjectsClientContract, + kibanaVersion: string +) { const objectsToExport = ids.map((id) => ({ id, type: 'dashboard' })); const objects = await collectReferencesDeep(savedObjectsClient, objectsToExport); return { - version: config.get('pkg.version'), + version: kibanaVersion, objects, }; } diff --git a/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts b/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts new file mode 100644 index 0000000000000..9d4dbb6067946 --- /dev/null +++ b/src/plugins/legacy_export/server/lib/import/import_dashboards.test.ts @@ -0,0 +1,92 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { SavedObject } from '../../../../../core/server'; +import { importDashboards } from './import_dashboards'; + +describe('importDashboards(req)', () => { + let savedObjectClient: ReturnType; + let importedObjects: SavedObject[]; + + beforeEach(() => { + savedObjectClient = savedObjectsClientMock.create(); + savedObjectClient.bulkCreate.mockResolvedValue({ saved_objects: [] }); + + importedObjects = [ + { id: 'dashboard-01', type: 'dashboard', attributes: { panelJSON: '{}' }, references: [] }, + { id: 'panel-01', type: 'visualization', attributes: { visState: '{}' }, references: [] }, + ]; + }); + + test('should call bulkCreate with each asset', async () => { + await importDashboards(savedObjectClient, importedObjects, { overwrite: false, exclude: [] }); + + expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith( + [ + { + id: 'dashboard-01', + type: 'dashboard', + attributes: { panelJSON: '{}' }, + references: [], + migrationVersion: {}, + }, + { + id: 'panel-01', + type: 'visualization', + attributes: { visState: '{}' }, + references: [], + migrationVersion: {}, + }, + ], + { overwrite: false } + ); + }); + + test('should call bulkCreate with overwrite true if force is truthy', async () => { + await importDashboards(savedObjectClient, importedObjects, { overwrite: true, exclude: [] }); + + expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(expect.any(Array), { + overwrite: true, + }); + }); + + test('should exclude types based on exclude argument', async () => { + await importDashboards(savedObjectClient, importedObjects, { + overwrite: false, + exclude: ['visualization'], + }); + + expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith( + [ + { + id: 'dashboard-01', + type: 'dashboard', + attributes: { panelJSON: '{}' }, + references: [], + migrationVersion: {}, + }, + ], + { overwrite: false } + ); + }); +}); diff --git a/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.js b/src/plugins/legacy_export/server/lib/import/import_dashboards.ts similarity index 81% rename from src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.js rename to src/plugins/legacy_export/server/lib/import/import_dashboards.ts index 7c28b184144f1..7b7562aecd7bd 100644 --- a/src/legacy/core_plugins/kibana/server/lib/import/import_dashboards.js +++ b/src/plugins/legacy_export/server/lib/import/import_dashboards.ts @@ -17,21 +17,19 @@ * under the License. */ -import { flatten } from 'lodash'; - -export async function importDashboards(req) { - const { payload } = req; - const overwrite = 'force' in req.query && req.query.force !== false; - const exclude = flatten([req.query.exclude]); - - const savedObjectsClient = req.getSavedObjectsClient(); +import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; +export async function importDashboards( + savedObjectsClient: SavedObjectsClientContract, + objects: SavedObject[], + { overwrite, exclude }: { overwrite: boolean; exclude: string[] } +) { // The server assumes that documents with no migrationVersion are up to date. // That assumption enables Kibana and other API consumers to not have to build // up migrationVersion prior to creating new objects. But it means that imports // need to set migrationVersion to something other than undefined, so that imported // docs are not seen as automatically up-to-date. - const docs = payload.objects + const docs = objects .filter((item) => !exclude.includes(item.type)) .map((doc) => ({ ...doc, migrationVersion: doc.migrationVersion || {} })); diff --git a/src/plugins/legacy_export/server/lib/index.ts b/src/plugins/legacy_export/server/lib/index.ts new file mode 100644 index 0000000000000..ceabdc76322e9 --- /dev/null +++ b/src/plugins/legacy_export/server/lib/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { exportDashboards } from './export/export_dashboards'; +export { importDashboards } from './import/import_dashboards'; diff --git a/src/legacy/core_plugins/kibana/server/routes/api/import/index.js b/src/plugins/legacy_export/server/plugin.ts similarity index 55% rename from src/legacy/core_plugins/kibana/server/routes/api/import/index.js rename to src/plugins/legacy_export/server/plugin.ts index b7efb7da3c5a9..22c7c1a05dddb 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/import/index.js +++ b/src/plugins/legacy_export/server/plugin.ts @@ -17,29 +17,26 @@ * under the License. */ -import Joi from 'joi'; -import { importDashboards } from '../../../lib/import/import_dashboards'; +import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server'; +import { registerRoutes } from './routes'; -export function importApi(server) { - server.route({ - path: '/api/kibana/dashboards/import', - method: ['POST'], - config: { - validate: { - payload: Joi.object().keys({ - objects: Joi.array(), - version: Joi.string(), - }), - query: Joi.object().keys({ - force: Joi.boolean().default(false), - exclude: [Joi.string(), Joi.array().items(Joi.string())], - }), - }, - tags: ['api'], - }, +export class LegacyExportPlugin implements Plugin<{}, {}> { + private readonly kibanaVersion: string; - handler: async (req) => { - return await importDashboards(req); - }, - }); + constructor(context: PluginInitializerContext) { + this.kibanaVersion = context.env.packageInfo.version; + } + + public setup({ http }: CoreSetup) { + const router = http.createRouter(); + registerRoutes(router, this.kibanaVersion); + + return {}; + } + + public start() { + return {}; + } + + public stop() {} } diff --git a/src/plugins/legacy_export/server/routes/export.ts b/src/plugins/legacy_export/server/routes/export.ts new file mode 100644 index 0000000000000..2064302eca432 --- /dev/null +++ b/src/plugins/legacy_export/server/routes/export.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'src/core/server'; +import { exportDashboards } from '../lib'; + +export const registerExportRoute = (router: IRouter, kibanaVersion: string) => { + router.get( + { + path: '/api/kibana/dashboards/export', + validate: { + query: schema.object({ + dashboard: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), + }), + }, + options: { + tags: ['api'], + }, + }, + async (ctx, req, res) => { + const ids = Array.isArray(req.query.dashboard) ? req.query.dashboard : [req.query.dashboard]; + const { client } = ctx.core.savedObjects; + + const exported = await exportDashboards(ids, client, kibanaVersion); + const filename = `kibana-dashboards.${moment.utc().format('YYYY-MM-DD-HH-mm-ss')}.json`; + const body = JSON.stringify(exported, null, ' '); + + return res.ok({ + body, + headers: { + 'Content-Disposition': `attachment; filename="${filename}"`, + 'Content-Type': 'application/json', + 'Content-Length': `${Buffer.byteLength(body, 'utf8')}`, + }, + }); + } + ); +}; diff --git a/src/plugins/legacy_export/server/routes/import.ts b/src/plugins/legacy_export/server/routes/import.ts new file mode 100644 index 0000000000000..cf6f28683be17 --- /dev/null +++ b/src/plugins/legacy_export/server/routes/import.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter, SavedObject } from 'src/core/server'; +import { importDashboards } from '../lib'; + +export const registerImportRoute = (router: IRouter) => { + router.post( + { + path: '/api/kibana/dashboards/import', + validate: { + body: schema.object({ + objects: schema.arrayOf(schema.recordOf(schema.string(), schema.any())), + version: schema.string(), + }), + query: schema.object({ + force: schema.boolean({ defaultValue: false }), + exclude: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { + defaultValue: [], + }), + }), + }, + options: { + tags: ['api'], + }, + }, + async (ctx, req, res) => { + const { client } = ctx.core.savedObjects; + const objects = req.body.objects as SavedObject[]; + const { force, exclude } = req.query; + const result = await importDashboards(client, objects, { + overwrite: force, + exclude: Array.isArray(exclude) ? exclude : [exclude], + }); + return res.ok({ + body: result, + }); + } + ); +}; diff --git a/src/plugins/legacy_export/server/routes/index.ts b/src/plugins/legacy_export/server/routes/index.ts new file mode 100644 index 0000000000000..7b9de7f016b6b --- /dev/null +++ b/src/plugins/legacy_export/server/routes/index.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IRouter } from 'src/core/server'; +import { registerImportRoute } from './import'; +import { registerExportRoute } from './export'; + +export const registerRoutes = (router: IRouter, kibanaVersion: string) => { + registerExportRoute(router, kibanaVersion); + registerImportRoute(router); +}; From c8608ed8107c84c33910a92c16f4a493609e05c3 Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Wed, 24 Jun 2020 10:25:48 +0200 Subject: [PATCH 31/36] Fixes #69344: Don't allow empty string for server.basePath config (#69377) * Fixes #69344: Don't allow empty string for server.basePath config * Remove unused basepath group --- .../server/http/__snapshots__/http_config.test.ts.snap | 2 ++ src/core/server/http/http_config.test.ts | 8 ++++++++ src/core/server/http/http_config.ts | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/server/http/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__snapshots__/http_config.test.ts.snap index 07c153a7a8a20..d48ead3cec8e1 100644 --- a/src/core/server/http/__snapshots__/http_config.test.ts.snap +++ b/src/core/server/http/__snapshots__/http_config.test.ts.snap @@ -83,6 +83,8 @@ Object { exports[`throws if basepath appends a slash 1`] = `"[basePath]: must start with a slash, don't end with one"`; +exports[`throws if basepath is an empty string 1`] = `"[basePath]: must start with a slash, don't end with one"`; + exports[`throws if basepath is missing prepended slash 1`] = `"[basePath]: must start with a slash, don't end with one"`; exports[`throws if basepath is not specified, but rewriteBasePath is set 1`] = `"cannot use [rewriteBasePath] when [basePath] is not specified"`; diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index eaf66219d08dc..0698f118be03f 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -78,6 +78,14 @@ test('throws if basepath appends a slash', () => { expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot(); }); +test('throws if basepath is an empty string', () => { + const httpSchema = config.schema; + const obj = { + basePath: '', + }; + expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot(); +}); + test('throws if basepath is not specified, but rewriteBasePath is set', () => { const httpSchema = config.schema; const obj = { diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 289b6539fd762..83a2e712b424f 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -23,7 +23,7 @@ import { hostname } from 'os'; import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; -const validBasePathRegex = /(^$|^\/.*[^\/]$)/; +const validBasePathRegex = /^\/.*[^\/]$/; const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const match = (regex: RegExp, errorMsg: string) => (str: string) => From fc9df7244bd49c698c21ec5149466b50f8608f39 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Wed, 24 Jun 2020 10:39:39 +0200 Subject: [PATCH 32/36] Redirect to Logged Out UI on SAML Logout Response. Prefer Login Selector UI to Logged Out UI whenever possible. (#69676) --- .../authentication/authenticator.test.ts | 27 +++++++++ .../server/authentication/authenticator.ts | 5 ++ .../authentication/providers/base.mock.ts | 8 +-- .../server/authentication/providers/base.ts | 3 + .../authentication/providers/basic.test.ts | 8 ++- .../authentication/providers/kerberos.test.ts | 4 +- .../authentication/providers/kerberos.ts | 4 +- .../authentication/providers/oidc.test.ts | 10 ++-- .../server/authentication/providers/oidc.ts | 4 +- .../authentication/providers/pki.test.ts | 4 +- .../server/authentication/providers/pki.ts | 4 +- .../authentication/providers/saml.test.ts | 56 ++++++++++--------- .../server/authentication/providers/saml.ts | 46 ++++++++++----- .../authentication/providers/token.test.ts | 13 +++-- .../server/routes/authentication/common.ts | 2 +- 15 files changed, 125 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 726ffb4dbb4e9..3b77ea3248173 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -112,6 +112,33 @@ describe('Authenticator', () => { ).toThrowError('Provider name "__http__" is reserved.'); }); + it('properly sets `loggedOut` URL.', () => { + const basicAuthenticationProviderMock = jest.requireMock('./providers/basic') + .BasicAuthenticationProvider; + + basicAuthenticationProviderMock.mockClear(); + new Authenticator(getMockOptions()); + expect(basicAuthenticationProviderMock).toHaveBeenCalledWith( + expect.objectContaining({ + urls: { + loggedOut: '/mock-server-basepath/security/logged_out', + }, + }), + expect.anything() + ); + + basicAuthenticationProviderMock.mockClear(); + new Authenticator(getMockOptions({ selector: { enabled: true } })); + expect(basicAuthenticationProviderMock).toHaveBeenCalledWith( + expect.objectContaining({ + urls: { + loggedOut: `/mock-server-basepath/login?msg=LOGGED_OUT`, + }, + }), + expect.anything() + ); + }); + describe('HTTP authentication provider', () => { beforeEach(() => { jest diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index ac5c2a72b9667..70f4063878aa8 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -242,6 +242,11 @@ export class Authenticator { client: this.options.clusterClient, logger: this.options.loggers.get('tokens'), }), + urls: { + loggedOut: options.config.authc.selector.enabled + ? `${options.basePath.serverBasePath}/login?msg=LOGGED_OUT` + : `${options.basePath.serverBasePath}/security/logged_out`, + }, }; this.providers = new Map( diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts index bab604e9e0c86..7c71348bb8ca0 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts @@ -15,14 +15,14 @@ export type MockAuthenticationProviderOptions = ReturnType< >; export function mockAuthenticationProviderOptions(options?: { name: string }) { - const basePath = httpServiceMock.createSetupContract().basePath; - basePath.get.mockReturnValue('/base-path'); - return { client: elasticsearchServiceMock.createClusterClient(), logger: loggingSystemMock.create().get(), - basePath, + basePath: httpServiceMock.createBasePath(), tokens: { refresh: jest.fn(), invalidate: jest.fn() }, name: options?.name ?? 'basic1', + urls: { + loggedOut: '/mock-server-basepath/security/logged_out', + }, }; } diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index d2d2e82951a3e..32ea41802d31b 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -26,6 +26,9 @@ export interface AuthenticationProviderOptions { client: IClusterClient; logger: Logger; tokens: PublicMethodsOf; + urls: { + loggedOut: string; + }; } /** diff --git a/x-pack/plugins/security/server/authentication/providers/basic.test.ts b/x-pack/plugins/security/server/authentication/providers/basic.test.ts index 97ca4e46d3eb5..ee6a12e36df05 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.test.ts @@ -107,7 +107,7 @@ describe('BasicAuthenticationProvider', () => { ) ).resolves.toEqual( AuthenticationResult.redirectTo( - '/base-path/login?next=%2Fbase-path%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded' + '/mock-server-basepath/login?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded' ) ); }); @@ -186,7 +186,7 @@ describe('BasicAuthenticationProvider', () => { it('always redirects to the login page.', async () => { await expect(provider.logout(httpServerMock.createKibanaRequest(), {})).resolves.toEqual( - DeauthenticationResult.redirectTo('/base-path/login?msg=LOGGED_OUT') + DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') ); }); @@ -199,7 +199,9 @@ describe('BasicAuthenticationProvider', () => { {} ) ).resolves.toEqual( - DeauthenticationResult.redirectTo('/base-path/login?next=%2Fapp%2Fml&msg=SESSION_EXPIRED') + DeauthenticationResult.redirectTo( + '/mock-server-basepath/login?next=%2Fapp%2Fml&msg=SESSION_EXPIRED' + ) ); }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index ca80761ee140c..ebf1341127e5f 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -518,7 +518,7 @@ describe('KerberosAuthenticationProvider', () => { expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair); }); - it('redirects to `/logged_out` page if tokens are invalidated successfully.', async () => { + it('redirects to `loggedOut` URL if tokens are invalidated successfully.', async () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'some-valid-token', @@ -528,7 +528,7 @@ describe('KerberosAuthenticationProvider', () => { mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 2540c21210bd5..66a0ce22d3d19 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -114,9 +114,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { return DeauthenticationResult.failed(err); } - return DeauthenticationResult.redirectTo( - `${this.options.basePath.serverBasePath}/security/logged_out` - ); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index 2d42d90ab60b8..74344e8ae3eda 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -353,7 +353,7 @@ describe('OIDCAuthenticationProvider', () => { state: { state: 'statevalue', nonce: 'noncevalue', - nextURL: '/base-path/s/foo/some-path', + nextURL: '/mock-server-basepath/s/foo/some-path', realm: 'oidc1', }, } @@ -575,7 +575,7 @@ describe('OIDCAuthenticationProvider', () => { state: { state: 'statevalue', nonce: 'noncevalue', - nextURL: '/base-path/s/foo/some-path', + nextURL: '/mock-server-basepath/s/foo/some-path', realm: 'oidc1', }, } @@ -702,7 +702,7 @@ describe('OIDCAuthenticationProvider', () => { }); }); - it('redirects to /logged_out if `redirect` field in OpenID Connect logout response is null.', async () => { + it('redirects to `loggedOut` URL if `redirect` field in OpenID Connect logout response is null.', async () => { const request = httpServerMock.createKibanaRequest(); const accessToken = 'x-oidc-token'; const refreshToken = 'x-oidc-refresh-token'; @@ -711,9 +711,7 @@ describe('OIDCAuthenticationProvider', () => { await expect( provider.logout(request, { accessToken, refreshToken, realm: 'oidc1' }) - ).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') - ); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', { diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index f8e6ac0f9b5d0..ac7374401f99a 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -433,9 +433,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { return DeauthenticationResult.redirectTo(redirect); } - return DeauthenticationResult.redirectTo( - `${this.options.basePath.serverBasePath}/security/logged_out` - ); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); } catch (err) { this.logger.debug(`Failed to deauthenticate user: ${err.message}`); return DeauthenticationResult.failed(err); diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 28db64edd9e32..a1279c9b9ca7f 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -547,14 +547,14 @@ describe('PKIAuthenticationProvider', () => { expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ accessToken: 'foo' }); }); - it('redirects to `/logged_out` page if access token is invalidated successfully.', async () => { + it('redirects to `loggedOut` URL if access token is invalidated successfully.', async () => { const request = httpServerMock.createKibanaRequest(); const state = { accessToken: 'foo', peerCertificateFingerprint256: '2A:7A:C2:DD' }; mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, state)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 243e5415ad2c2..164a9516f0695 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -119,9 +119,7 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { return DeauthenticationResult.failed(err); } - return DeauthenticationResult.redirectTo( - `${this.options.basePath.serverBasePath}/security/logged_out` - ); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index f8e735a658a20..f7adaa24e9dbb 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -208,7 +208,7 @@ describe('SAMLAuthenticationProvider', () => { { requestId: 'some-request-id', redirectURL: '', realm: 'test-realm' } ) ).resolves.toEqual( - AuthenticationResult.redirectTo('/base-path/', { + AuthenticationResult.redirectTo('/mock-server-basepath/', { state: { accessToken: 'user-initiated-login-token', refreshToken: 'user-initiated-login-refresh-token', @@ -247,7 +247,7 @@ describe('SAMLAuthenticationProvider', () => { { requestId: 'some-request-id', redirectURL: '', realm: 'test-realm' } ) ).resolves.toEqual( - AuthenticationResult.redirectTo('/base-path/', { + AuthenticationResult.redirectTo('/mock-server-basepath/', { state: { accessToken: 'user-initiated-login-token', refreshToken: 'user-initiated-login-refresh-token', @@ -276,7 +276,7 @@ describe('SAMLAuthenticationProvider', () => { samlResponse: 'saml-response-xml', }) ).resolves.toEqual( - AuthenticationResult.redirectTo('/base-path/', { + AuthenticationResult.redirectTo('/mock-server-basepath/', { state: { accessToken: 'idp-initiated-login-token', refreshToken: 'idp-initiated-login-refresh-token', @@ -562,7 +562,7 @@ describe('SAMLAuthenticationProvider', () => { state ) ).resolves.toEqual( - AuthenticationResult.redirectTo('/base-path/', { + AuthenticationResult.redirectTo('/mock-server-basepath/', { state: { username: 'user', accessToken: 'new-valid-token', @@ -999,7 +999,7 @@ describe('SAMLAuthenticationProvider', () => { await expect(provider.authenticate(request)).resolves.toEqual( AuthenticationResult.redirectTo( '/mock-server-basepath/internal/security/saml/capture-url-fragment', - { state: { redirectURL: '/base-path/s/foo/some-path', realm: 'test-realm' } } + { state: { redirectURL: '/mock-server-basepath/s/foo/some-path', realm: 'test-realm' } } ) ); @@ -1029,7 +1029,7 @@ describe('SAMLAuthenticationProvider', () => { expect(mockOptions.logger.warn).toHaveBeenCalledTimes(1); expect(mockOptions.logger.warn).toHaveBeenCalledWith( - 'Max URL path size should not exceed 100b but it was 107b. URL is not captured.' + 'Max URL path size should not exceed 100b but it was 118b. URL is not captured.' ); }); @@ -1274,7 +1274,7 @@ describe('SAMLAuthenticationProvider', () => { await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.redirectTo( '/mock-server-basepath/internal/security/saml/capture-url-fragment', - { state: { redirectURL: '/base-path/s/foo/some-path', realm: 'test-realm' } } + { state: { redirectURL: '/mock-server-basepath/s/foo/some-path', realm: 'test-realm' } } ) ); @@ -1330,7 +1330,7 @@ describe('SAMLAuthenticationProvider', () => { expect(mockOptions.logger.warn).toHaveBeenCalledTimes(1); expect(mockOptions.logger.warn).toHaveBeenCalledWith( - 'Max URL path size should not exceed 100b but it was 107b. URL is not captured.' + 'Max URL path size should not exceed 100b but it was 118b. URL is not captured.' ); }); @@ -1400,7 +1400,7 @@ describe('SAMLAuthenticationProvider', () => { }); }); - it('redirects to /security/logged_out if `redirect` field in SAML logout response is null.', async () => { + it('redirects to `loggedOut` URL if `redirect` field in SAML logout response is null.', async () => { const request = httpServerMock.createKibanaRequest(); const accessToken = 'x-saml-token'; const refreshToken = 'x-saml-refresh-token'; @@ -1414,9 +1414,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') - ); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1424,7 +1422,7 @@ describe('SAMLAuthenticationProvider', () => { }); }); - it('redirects to /security/logged_out if `redirect` field in SAML logout response is not defined.', async () => { + it('redirects to `loggedOut` URL if `redirect` field in SAML logout response is not defined.', async () => { const request = httpServerMock.createKibanaRequest(); const accessToken = 'x-saml-token'; const refreshToken = 'x-saml-refresh-token'; @@ -1438,9 +1436,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') - ); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1450,7 +1446,7 @@ describe('SAMLAuthenticationProvider', () => { it('relies on SAML logout if query string is not empty, but does not include SAMLRequest.', async () => { const request = httpServerMock.createKibanaRequest({ - query: { Whatever: 'something unrelated' }, + query: { Whatever: 'something unrelated', SAMLResponse: 'xxx yyy' }, }); const accessToken = 'x-saml-token'; const refreshToken = 'x-saml-refresh-token'; @@ -1464,9 +1460,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') - ); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1486,9 +1480,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken: 'x-saml-refresh-token', realm: 'test-realm', }) - ).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') - ); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', { @@ -1496,13 +1488,13 @@ describe('SAMLAuthenticationProvider', () => { }); }); - it('redirects to /security/logged_out if `redirect` field in SAML invalidate response is null.', async () => { + it('redirects to `loggedOut` URL if `redirect` field in SAML invalidate response is null.', async () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } }); mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null }); await expect(provider.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) ); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); @@ -1511,13 +1503,13 @@ describe('SAMLAuthenticationProvider', () => { }); }); - it('redirects to /security/logged_out if `redirect` field in SAML invalidate response is not defined.', async () => { + it('redirects to `loggedOut` URL if `redirect` field in SAML invalidate response is not defined.', async () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } }); mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: undefined }); await expect(provider.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/security/logged_out') + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) ); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); @@ -1526,6 +1518,16 @@ describe('SAMLAuthenticationProvider', () => { }); }); + it('redirects to `loggedOut` URL if SAML logout response is received.', async () => { + const request = httpServerMock.createKibanaRequest({ query: { SAMLResponse: 'xxx yyy' } }); + + await expect(provider.logout(request)).resolves.toEqual( + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + ); + + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + }); + it('redirects user to the IdP if SLO is supported by IdP in case of SP initiated logout.', async () => { const request = httpServerMock.createKibanaRequest(); const accessToken = 'x-saml-token'; diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 9a4b7dd679ccb..d121cd4979aa7 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -70,6 +70,14 @@ function isSAMLRequestQuery(query: any): query is { SAMLRequest: string } { return query && query.SAMLRequest; } +/** + * Checks whether request query includes SAML response from IdP. + * @param query Parsed HTTP request query. + */ +function isSAMLResponseQuery(query: any): query is { SAMLResponse: string } { + return query && query.SAMLResponse; +} + /** * Checks whether current request can initiate new session. * @param request Request instance. @@ -247,22 +255,36 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug(`Trying to log user out via ${request.url.path}.`); // Normally when there is no active session in Kibana, `logout` method shouldn't do anything - // and user will eventually be redirected to the home page to log in. But when SAML is enabled - // there is a special case when logout is initiated by the IdP or another SP, then IdP will - // request _every_ SP associated with the current user session to do the logout. So if Kibana, - // without an active session, receives such request it shouldn't redirect user to the home page, - // but rather redirect back to IdP with correct logout response and only Elasticsearch knows how - // to do that. - const isIdPInitiatedSLO = isSAMLRequestQuery(request.query); - if (!state?.accessToken && !isIdPInitiatedSLO) { + // and user will eventually be redirected to the home page to log in. But when SAML SLO is + // supported there are two special cases that we need to handle even if there is no active + // Kibana session: + // + // 1. When IdP or another SP initiates logout, then IdP will request _every_ SP associated with + // the current user session to do the logout. So if Kibana receives such request it shouldn't + // redirect user to the home page, but rather redirect back to IdP with correct logout response + // and only Elasticsearch knows how to do that. + // + // 2. When Kibana initiates logout, then IdP may eventually respond with the logout response. So + // if Kibana receives such response it shouldn't redirect user to the home page, but rather + // redirect to the `loggedOut` URL instead. + const isIdPInitiatedSLORequest = isSAMLRequestQuery(request.query); + const isSPInitiatedSLOResponse = isSAMLResponseQuery(request.query); + if (!state?.accessToken && !isIdPInitiatedSLORequest && !isSPInitiatedSLOResponse) { this.logger.debug('There is no SAML session to invalidate.'); return DeauthenticationResult.notHandled(); } try { - const redirect = isIdPInitiatedSLO + // It may _theoretically_ (highly unlikely in practice though) happen that when user receives + // logout response they may already have a new SAML session (isSPInitiatedSLOResponse == true + // and state !== undefined). In this case case it'd be safer to trigger SP initiated logout + // for the new session as well. + const redirect = isIdPInitiatedSLORequest ? await this.performIdPInitiatedSingleLogout(request) - : await this.performUserInitiatedSingleLogout(state?.accessToken!, state?.refreshToken!); + : state + ? await this.performUserInitiatedSingleLogout(state.accessToken!, state.refreshToken!) + : // Once Elasticsearch can consume logout response we'll be sending it here. See https://github.com/elastic/elasticsearch/issues/40901 + null; // Having non-null `redirect` field within logout response means that IdP // supports SAML Single Logout and we should redirect user to the specified @@ -272,9 +294,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { return DeauthenticationResult.redirectTo(redirect); } - return DeauthenticationResult.redirectTo( - `${this.options.basePath.serverBasePath}/security/logged_out` - ); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); } catch (err) { this.logger.debug(`Failed to deauthenticate user: ${err.message}`); return DeauthenticationResult.failed(err); diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 92cea424e575d..84ff7d1f5a1ef 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -179,7 +179,7 @@ describe('TokenAuthenticationProvider', () => { ) ).resolves.toEqual( AuthenticationResult.redirectTo( - '/base-path/login?next=%2Fbase-path%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded' + '/mock-server-basepath/login?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded' ) ); }); @@ -309,9 +309,10 @@ describe('TokenAuthenticationProvider', () => { mockOptions.tokens.refresh.mockResolvedValue(null); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( - AuthenticationResult.redirectTo('/base-path/login?next=%2Fbase-path%2Fsome-path', { - state: null, - }) + AuthenticationResult.redirectTo( + '/mock-server-basepath/login?next=%2Fmock-server-basepath%2Fsome-path', + { state: null } + ) ); expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); @@ -455,7 +456,7 @@ describe('TokenAuthenticationProvider', () => { mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo('/base-path/login?msg=LOGGED_OUT') + DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); @@ -469,7 +470,7 @@ describe('TokenAuthenticationProvider', () => { mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo('/base-path/login?yep=nope') + DeauthenticationResult.redirectTo('/mock-server-basepath/login?yep=nope') ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/routes/authentication/common.ts b/x-pack/plugins/security/server/routes/authentication/common.ts index 91783140539a5..ad38a158af2b9 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.ts @@ -31,7 +31,7 @@ export function defineCommonRoutes({ { path, // Allow unknown query parameters as this endpoint can be hit by the 3rd-party with any - // set of query string parameters (e.g. SAML/OIDC logout request parameters). + // set of query string parameters (e.g. SAML/OIDC logout request/response parameters). validate: { query: schema.object({}, { unknowns: 'allow' }) }, options: { authRequired: false }, }, From d1a6fa26b88ff6ddb557fca70b17ba58243760e6 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 24 Jun 2020 11:26:19 +0200 Subject: [PATCH 33/36] Properly redirect legacy URLs (#68284) --- .../kbn-storybook/lib/webpack.dll.config.js | 1 - src/legacy/core_plugins/kibana/index.js | 32 ---- .../public/__tests__/vis_type_table/legacy.ts | 1 + .../core_plugins/kibana/public/index.scss | 9 -- .../core_plugins/kibana/public/kibana.js | 59 -------- .../local_application_service/_index.scss | 1 - .../_local_application_service.scss | 5 - .../public/local_application_service/index.ts | 20 --- .../local_application_service.ts | 134 ----------------- .../kibana/public/management/index.scss | 13 -- .../sections/index_patterns/index.scss | 25 ---- .../core_plugins/timelion/public/legacy.ts | 4 +- .../core_plugins/timelion/public/plugin.ts | 13 +- src/legacy/ui/public/autoload/all.js | 1 - src/legacy/ui/public/autoload/styles.js | 20 --- .../directives/__tests__/input_focus.js | 3 + .../new_platform/new_platform.karma_mock.js | 4 +- src/legacy/ui/public/styles/font_awesome.less | 10 -- src/legacy/ui/ui_render/ui_render_mixin.js | 9 +- src/optimize/base_optimizer.js | 7 - ..._settings.scss => _advanced_settings.scss} | 0 .../{_index.scss => index.scss} | 0 .../mount_management_section.tsx | 2 + .../public/application/application.ts | 3 +- .../public/application/legacy_app.js | 14 +- src/plugins/dashboard/public/plugin.tsx | 31 ++-- .../index_patterns/index_pattern.ts | 2 +- .../data/server/saved_objects/search.ts | 2 +- .../discover/public/application/_hacks.scss | 2 - .../public/application/angular/discover.js | 15 +- .../angular/doc_table/components/table_row.ts | 5 +- .../public/application/angular/index.ts | 1 + .../public/application/angular/redirect.ts} | 22 ++- .../public/application/application.ts | 3 + src/plugins/discover/public/build_services.ts | 3 + .../discover/public/kibana_services.ts | 1 + src/plugins/discover/public/plugin.ts | 22 ++- .../public/saved_searches/_saved_search.ts | 2 +- .../public/saved_searches/saved_searches.ts | 2 +- .../public/font_awesome/font_awesome.scss | 23 +++ .../public/font_awesome/index.ts} | 2 +- .../public/forward_app/forward_app.ts | 16 +- .../navigate_to_legacy_kibana_url.test.ts | 20 ++- .../navigate_to_legacy_kibana_url.ts | 30 ++-- src/plugins/kibana_legacy/public/mocks.ts | 14 +- .../public/navigate_to_default_app.ts | 7 +- src/plugins/kibana_legacy/public/plugin.ts | 141 ++++-------------- .../kibana_legacy/public/utils/index.ts | 1 + .../public/utils/normalize_path.ts} | 12 +- .../state_management/url/kbn_url_tracker.ts | 12 ++ src/plugins/maps_legacy/public/_index.scss | 1 - src/plugins/maps_legacy/public/index.ts | 2 + .../public/map/{_index.scss => index.scss} | 0 src/plugins/region_map/kibana.json | 1 + .../region_map/public/kibana_services.ts | 5 + src/plugins/region_map/public/plugin.ts | 7 +- .../public/region_map_visualization.js | 3 +- .../saved_objects_management/public/index.ts | 1 + .../saved_objects/kibana_app_migration.ts | 38 ----- src/plugins/share/server/saved_objects/url.ts | 7 +- src/plugins/tile_map/kibana.json | 1 + src/plugins/tile_map/public/plugin.ts | 6 +- src/plugins/tile_map/public/services.ts | 5 + .../tile_map/public/tile_map_visualization.js | 7 +- src/plugins/vis_type_table/kibana.json | 3 +- src/plugins/vis_type_table/public/plugin.ts | 7 +- src/plugins/vis_type_table/public/services.ts | 5 + .../vis_type_table/public/vis_controller.ts | 2 + src/plugins/vis_type_vislib/kibana.json | 2 +- src/plugins/vis_type_vislib/public/plugin.ts | 7 +- .../vis_type_vislib/public/services.ts | 5 + .../vis_type_vislib/public/vis_controller.tsx | 3 + .../public/application/legacy_app.js | 14 +- .../visualize/public/kibana_services.ts | 1 + src/plugins/visualize/public/plugin.ts | 9 +- .../apis/saved_objects_management/find.ts | 2 +- .../saved_objects_management/relationships.ts | 8 +- test/functional/apps/bundles/index.js | 2 +- test/functional/apps/dashboard/index.js | 1 + test/functional/apps/dashboard/legacy_urls.ts | 111 ++++++++++++++ .../functional/apps/discover/_shared_links.js | 2 +- .../page_objects/visualize_editor_page.ts | 4 + x-pack/plugins/graph/kibana.json | 2 +- x-pack/plugins/graph/public/application.ts | 5 +- x-pack/plugins/graph/public/plugin.ts | 7 +- x-pack/plugins/ml/kibana.json | 3 +- x-pack/plugins/ml/public/application/app.tsx | 2 + x-pack/plugins/ml/public/plugin.ts | 3 + .../monitoring/public/angular/index.ts | 12 +- x-pack/plugins/monitoring/public/plugin.ts | 2 + x-pack/plugins/monitoring/public/types.ts | 2 + .../components/copy_to_space_flyout.tsx | 8 +- .../copy_to_space_flyout_footer.tsx | 2 +- .../components/processing_copy_to_space.tsx | 6 +- .../summarize_copy_result.test.ts | 2 +- .../summarize_copy_result.ts | 6 +- 96 files changed, 473 insertions(+), 642 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/kibana.js delete mode 100644 src/legacy/core_plugins/kibana/public/local_application_service/_index.scss delete mode 100644 src/legacy/core_plugins/kibana/public/local_application_service/_local_application_service.scss delete mode 100644 src/legacy/core_plugins/kibana/public/local_application_service/index.ts delete mode 100644 src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts delete mode 100644 src/legacy/core_plugins/kibana/public/management/index.scss delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.scss delete mode 100644 src/legacy/ui/public/autoload/styles.js delete mode 100644 src/legacy/ui/public/styles/font_awesome.less rename src/plugins/advanced_settings/public/management_app/{advanced_settings.scss => _advanced_settings.scss} (100%) rename src/plugins/advanced_settings/public/management_app/{_index.scss => index.scss} (100%) rename src/{legacy/core_plugins/kibana/public/index.ts => plugins/discover/public/application/angular/redirect.ts} (54%) create mode 100644 src/plugins/kibana_legacy/public/font_awesome/font_awesome.scss rename src/plugins/{advanced_settings/public/_index.scss => kibana_legacy/public/font_awesome/index.ts} (95%) rename src/{legacy/core_plugins/kibana/inject_vars.js => plugins/kibana_legacy/public/utils/normalize_path.ts} (73%) delete mode 100644 src/plugins/maps_legacy/public/_index.scss rename src/plugins/maps_legacy/public/map/{_index.scss => index.scss} (100%) delete mode 100644 src/plugins/share/server/saved_objects/kibana_app_migration.ts create mode 100644 test/functional/apps/dashboard/legacy_urls.ts diff --git a/packages/kbn-storybook/lib/webpack.dll.config.js b/packages/kbn-storybook/lib/webpack.dll.config.js index bc871fab471b2..534f503e2956a 100644 --- a/packages/kbn-storybook/lib/webpack.dll.config.js +++ b/packages/kbn-storybook/lib/webpack.dll.config.js @@ -73,7 +73,6 @@ module.exports = { 'rxjs', 'sinon', 'tinycolor2', - './src/legacy/ui/public/styles/font_awesome.less', './src/legacy/ui/public/styles/bootstrap/bootstrap_light.less', ], plugins: [ diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index c70a1135a2c41..9648ff29a95e7 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -23,9 +23,6 @@ import { promisify } from 'util'; import { getUiSettingDefaults } from './server/ui_setting_defaults'; import { registerCspCollector } from './server/lib/csp_usage_collector'; -import { injectVars } from './inject_vars'; - -import { kbnBaseUrl } from '../../../plugins/kibana_legacy/server'; const mkdirAsync = promisify(Fs.mkdir); @@ -43,35 +40,7 @@ export default function (kibana) { }, uiExports: { - app: { - id: 'kibana', - title: 'Kibana', - listed: false, - main: 'plugins/kibana/kibana', - }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), - links: [], - - injectDefaultVars(server, options) { - const mapConfig = server.config().get('map'); - const tilemap = mapConfig.tilemap; - - return { - kbnIndex: options.index, - kbnBaseUrl, - - // required on all pages due to hacks that use these values - mapConfig, - tilemapsConfig: { - deprecated: { - // If url is set, old settings must be used for backward compatibility - isOverridden: typeof tilemap.url === 'string' && tilemap.url !== '', - config: tilemap, - }, - }, - }; - }, - uiSettingDefaults: getUiSettingDefaults(), }, @@ -90,7 +59,6 @@ export default function (kibana) { init: async function (server) { const { usageCollection } = server.newPlatform.setup.plugins; registerCspCollector(usageCollection, server); - server.injectUiAppVars('kibana', () => injectVars(server)); }, }); } diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts index c6467a5beae68..216afe5920408 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts @@ -35,4 +35,5 @@ const pluginInstance = new TableVisPlugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, plugins); export const start = pluginInstance.start(npStart.core, { data: npStart.plugins.data, + kibanaLegacy: npStart.plugins.kibanaLegacy, }); diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 56a2543dbca78..e9810a747c8c7 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -7,12 +7,3 @@ // Public UI styles @import 'src/legacy/ui/public/index'; -// Has to come after visualize because of some -// bad cascading in the Editor layout -@import '../../../../plugins/maps_legacy/public/index'; - -// Management styles -@import './management/index'; - -// Local application mount wrapper styles -@import 'local_application_service/index'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js deleted file mode 100644 index 51dedcc629c76..0000000000000 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// autoloading - -// preloading (for faster webpack builds) -import routes from 'ui/routes'; -import { npSetup } from 'ui/new_platform'; - -// import the uiExports that we want to "use" -import 'uiExports/savedObjectTypes'; -import 'uiExports/fieldFormatEditors'; -import 'uiExports/navbarExtensions'; -import 'uiExports/contextMenuActions'; -import 'uiExports/managementSections'; -import 'uiExports/indexManagement'; -import 'uiExports/embeddableFactories'; -import 'uiExports/embeddableActions'; -import 'uiExports/inspectorViews'; -import 'uiExports/search'; -import 'uiExports/shareContextMenuExtensions'; -import 'uiExports/interpreter'; - -import 'ui/autoload/all'; - -import { localApplicationService } from './local_application_service'; - -npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('doc', 'discover', { keepPrefix: true }); -npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('context', 'discover', { keepPrefix: true }); - -npSetup.plugins.kibanaLegacy.forwardApp('management', 'management', (path) => { - return path.replace('/management', ''); -}); - -localApplicationService.attachToAngular(routes); - -routes.enable(); - -const { config } = npSetup.plugins.kibanaLegacy; - -routes.otherwise({ - redirectTo: `/${config.defaultAppId || 'discover'}`, -}); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/_index.scss b/src/legacy/core_plugins/kibana/public/local_application_service/_index.scss deleted file mode 100644 index 12cc1444101e7..0000000000000 --- a/src/legacy/core_plugins/kibana/public/local_application_service/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'local_application_service'; diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/_local_application_service.scss b/src/legacy/core_plugins/kibana/public/local_application_service/_local_application_service.scss deleted file mode 100644 index 33a6100c43975..0000000000000 --- a/src/legacy/core_plugins/kibana/public/local_application_service/_local_application_service.scss +++ /dev/null @@ -1,5 +0,0 @@ -.kbnLocalApplicationWrapper { - display: flex; - flex-direction: column; - flex-grow: 1; -} diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/index.ts b/src/legacy/core_plugins/kibana/public/local_application_service/index.ts deleted file mode 100644 index 2128355ca906a..0000000000000 --- a/src/legacy/core_plugins/kibana/public/local_application_service/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './local_application_service'; diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts deleted file mode 100644 index 59e5238578d25..0000000000000 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { App, AppUnmount, AppMountDeprecated } from 'kibana/public'; -import { UIRoutes } from 'ui/routes'; -import { ILocationService, IScope } from 'angular'; -import { npStart } from 'ui/new_platform'; -import { htmlIdGenerator } from '@elastic/eui'; - -const matchAllWithPrefix = (prefixOrApp: string | App) => - `/${typeof prefixOrApp === 'string' ? prefixOrApp : prefixOrApp.id}/:tail*?`; - -/** - * To be able to migrate and shim parts of the Kibana app plugin - * while still running some parts of it in the legacy world, this - * service emulates the core application service while using the global - * angular router to switch between apps without page reload. - * - * The id of the apps is used as prefix of the route - when switching between - * to apps, the current application is unmounted. - * - * This service becomes unnecessary once the platform provides a central - * router that handles switching between applications without page reload. - */ -export class LocalApplicationService { - private idGenerator = htmlIdGenerator('kibanaAppLocalApp'); - - /** - * Wires up listeners to handle mounting and unmounting of apps to - * the legacy angular route manager. Once all apps within the Kibana - * plugin are using the local route manager, this implementation can - * be switched to a more lightweight implementation. - * - * @param angularRouteManager The current `ui/routes` instance - */ - attachToAngular(angularRouteManager: UIRoutes) { - npStart.plugins.kibanaLegacy.getApps().forEach((app) => { - const wrapperElementId = this.idGenerator(); - angularRouteManager.when(matchAllWithPrefix(app), { - outerAngularWrapperRoute: true, - reloadOnSearch: false, - reloadOnUrl: false, - template: `
`, - controller($scope: IScope) { - const element = document.getElementById(wrapperElementId)!; - let unmountHandler: AppUnmount | null = null; - let isUnmounted = false; - $scope.$on('$destroy', () => { - if (unmountHandler) { - unmountHandler(); - } - isUnmounted = true; - }); - (async () => { - const params = { - element, - appBasePath: '', - onAppLeave: () => undefined, - // TODO: adapt to use Core's ScopedHistory - history: {} as any, - }; - unmountHandler = isAppMountDeprecated(app.mount) - ? await app.mount({ core: npStart.core }, params) - : await app.mount(params); - // immediately unmount app if scope got destroyed in the meantime - if (isUnmounted) { - unmountHandler(); - } - })(); - }, - }); - - if (app.updater$) { - app.updater$.subscribe((updater) => { - const updatedFields = updater(app); - if (updatedFields && updatedFields.activeUrl) { - npStart.core.chrome.navLinks.update(app.navLinkId || app.id, { - url: updatedFields.activeUrl, - }); - } - }); - } - }); - - npStart.plugins.kibanaLegacy.getForwards().forEach((forwardDefinition) => { - angularRouteManager.when(matchAllWithPrefix(forwardDefinition.legacyAppId), { - outerAngularWrapperRoute: true, - reloadOnSearch: false, - reloadOnUrl: false, - template: '', - controller($location: ILocationService) { - const newPath = forwardDefinition.rewritePath($location.url()); - window.location.replace( - npStart.core.http.basePath.prepend(`/app/${forwardDefinition.newAppId}${newPath}`) - ); - }, - }); - }); - - npStart.plugins.kibanaLegacy - .getLegacyAppAliases() - .forEach(({ legacyAppId, newAppId, keepPrefix }) => { - angularRouteManager.when(matchAllWithPrefix(legacyAppId), { - resolveRedirectTo: ($location: ILocationService) => { - const url = $location.url(); - return `/${newAppId}${keepPrefix ? url : url.replace(legacyAppId, '')}`; - }, - }); - }); - } -} - -export const localApplicationService = new LocalApplicationService(); - -function isAppMountDeprecated(mount: (...args: any[]) => any): mount is AppMountDeprecated { - // Mount functions with two arguments are assumed to expect deprecated `context` object. - return mount.length === 2; -} diff --git a/src/legacy/core_plugins/kibana/public/management/index.scss b/src/legacy/core_plugins/kibana/public/management/index.scss deleted file mode 100644 index fb267b714f1c9..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/index.scss +++ /dev/null @@ -1,13 +0,0 @@ -// This file is imported into src/core_plugings/kibana/publix/index.scss - -// Prefix all styles with "dsh" to avoid conflicts. -// Examples -// mgtChart -// mgtChart__legend -// mgtChart__legend--small -// mgtChart__legend-isLoading - -// Core -@import '../../../../../plugins/advanced_settings/public/index'; - -@import 'sections/index_patterns/index'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.scss b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.scss deleted file mode 100644 index c5cf844ebdc34..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.scss +++ /dev/null @@ -1,25 +0,0 @@ -#indexPatternListReact { - display: flex; - - .indexPatternList__headerWrapper { - padding-bottom: $euiSizeS; - } - - .euiButtonEmpty__content { - justify-content: left; - padding: 0; - - span { - text-overflow: ellipsis; - overflow: hidden; - } - } - - .indexPatternListPrompt__descList { - text-align: left; - } -} - -.indexPatternList__badge { - margin-left: $euiSizeS; -} diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index acb95e80fe18c..7980291e2d462 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -18,7 +18,7 @@ */ import { PluginInitializerContext } from 'kibana/public'; -import { npSetup } from 'ui/new_platform'; +import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from '.'; import { TimelionPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; @@ -32,4 +32,4 @@ const setupPlugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 8b021cda4bfb0..1f837303a2b3d 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -16,10 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { CoreSetup, Plugin, PluginInitializerContext, IUiSettingsClient } from 'kibana/public'; +import { + CoreSetup, + Plugin, + PluginInitializerContext, + IUiSettingsClient, + CoreStart, +} from 'kibana/public'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; +import { KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { @@ -59,7 +66,9 @@ export class TimelionPlugin implements Plugin, void> { dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); } - public start() {} + public start(core: CoreStart, { kibanaLegacy }: { kibanaLegacy: KibanaLegacyStart }) { + kibanaLegacy.loadFontAwesome(); + } public stop(): void {} } diff --git a/src/legacy/ui/public/autoload/all.js b/src/legacy/ui/public/autoload/all.js index 5c95afb7a0628..be9b29aa944c9 100644 --- a/src/legacy/ui/public/autoload/all.js +++ b/src/legacy/ui/public/autoload/all.js @@ -20,4 +20,3 @@ import './accessibility'; import './modules'; import './settings'; -import './styles'; diff --git a/src/legacy/ui/public/autoload/styles.js b/src/legacy/ui/public/autoload/styles.js deleted file mode 100644 index c623acca07b01..0000000000000 --- a/src/legacy/ui/public/autoload/styles.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import 'ui/styles/font_awesome.less'; diff --git a/src/legacy/ui/public/directives/__tests__/input_focus.js b/src/legacy/ui/public/directives/__tests__/input_focus.js index 45b1821cbfd21..a9cd9b3c87974 100644 --- a/src/legacy/ui/public/directives/__tests__/input_focus.js +++ b/src/legacy/ui/public/directives/__tests__/input_focus.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import $ from 'jquery'; import '../input_focus'; +import uiRoutes from 'ui/routes'; describe('Input focus directive', function () { let $compile; @@ -32,6 +33,8 @@ describe('Input focus directive', function () { let selectedText; const inputValue = 'Input Text Value'; + uiRoutes.enable(); + beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function (_$compile_, _$rootScope_, _$timeout_) { diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index d98770842a0f0..35380ada51a0a 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -263,7 +263,6 @@ export const npSetup = { }, kibanaLegacy: { registerLegacyApp: () => {}, - registerLegacyAppAlias: () => {}, forwardApp: () => {}, config: { defaultAppId: 'home', @@ -379,9 +378,8 @@ export const npStart = { registerType: sinon.fake(), }, kibanaLegacy: { - getApps: () => [], getForwards: () => [], - getLegacyAppAliases: () => [], + loadFontAwesome: () => {}, config: { defaultAppId: 'home', }, diff --git a/src/legacy/ui/public/styles/font_awesome.less b/src/legacy/ui/public/styles/font_awesome.less deleted file mode 100644 index 428e3c6b83f89..0000000000000 --- a/src/legacy/ui/public/styles/font_awesome.less +++ /dev/null @@ -1,10 +0,0 @@ -// Needs to remain a LESS file to point to the correct path for the fonts themeselves -@import "~font-awesome/less/font-awesome"; - -// new file icon -.@{fa-css-prefix}-file-new-o:before { content: @fa-var-file-o; } -.@{fa-css-prefix}-file-new-o:after { content: @fa-var-plus; position: relative; margin-left: -1.0em; font-size: 0.5em; } - -// alias for alert types - allows class="fa fa-{{alertType}}" -.fa-success:before { content: @fa-var-check; } -.fa-danger:before { content: @fa-var-exclamation-circle; } diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index b4f8255297240..0cfcb91aa94ef 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -130,7 +130,6 @@ export function uiRenderMixin(kbnServer, server, config) { `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, `${regularBundlePath}/light_theme.style.css`, ]), - `${regularBundlePath}/commons.style.css`, ...(isCore ? [] : [ @@ -155,13 +154,7 @@ export function uiRenderMixin(kbnServer, server, config) { (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` ), `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, - ...(isCore - ? [] - : [ - `${dllBundlePath}/vendors_runtime.bundle.dll.js`, - ...dllJsChunks, - `${regularBundlePath}/commons.bundle.js`, - ]), + ...(isCore ? [] : [`${dllBundlePath}/vendors_runtime.bundle.dll.js`, ...dllJsChunks]), `${regularBundlePath}/core/core.entry.js`, ...kpPluginIds.map( diff --git a/src/optimize/base_optimizer.js b/src/optimize/base_optimizer.js index 12131b89e03c1..55752db55e28a 100644 --- a/src/optimize/base_optimizer.js +++ b/src/optimize/base_optimizer.js @@ -266,13 +266,6 @@ export default class BaseOptimizer { optimization: { splitChunks: { cacheGroups: { - commons: { - name: 'commons', - chunks: (chunk) => - chunk.canBeInitial() && chunk.name !== 'light_theme' && chunk.name !== 'dark_theme', - minChunks: 2, - reuseExistingChunk: true, - }, light_theme: { name: 'light_theme', test: (m) => diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.scss b/src/plugins/advanced_settings/public/management_app/_advanced_settings.scss similarity index 100% rename from src/plugins/advanced_settings/public/management_app/advanced_settings.scss rename to src/plugins/advanced_settings/public/management_app/_advanced_settings.scss diff --git a/src/plugins/advanced_settings/public/management_app/_index.scss b/src/plugins/advanced_settings/public/management_app/index.scss similarity index 100% rename from src/plugins/advanced_settings/public/management_app/_index.scss rename to src/plugins/advanced_settings/public/management_app/index.scss diff --git a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx index b4779d051ab02..ab348451b1eef 100644 --- a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx +++ b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx @@ -29,6 +29,8 @@ import { AdvancedSettings } from './advanced_settings'; import { ManagementAppMountParams } from '../../../management/public'; import { ComponentRegistry } from '../types'; +import './index.scss'; + const title = i18n.translate('advancedSettings.advancedSettingsLabel', { defaultMessage: 'Advanced Settings', }); diff --git a/src/plugins/dashboard/public/application/application.ts b/src/plugins/dashboard/public/application/application.ts index 543450916c505..08eeb19dcda93 100644 --- a/src/plugins/dashboard/public/application/application.ts +++ b/src/plugins/dashboard/public/application/application.ts @@ -68,11 +68,12 @@ export interface RenderDeps { embeddable: EmbeddableStart; localStorage: Storage; share?: SharePluginStart; - config: KibanaLegacyStart['config']; usageCollection?: UsageCollectionSetup; navigateToDefaultApp: KibanaLegacyStart['navigateToDefaultApp']; + navigateToLegacyKibanaUrl: KibanaLegacyStart['navigateToLegacyKibanaUrl']; scopedHistory: () => ScopedHistory; savedObjects: SavedObjectsStart; + restorePreviousUrl: () => void; } let angularModuleInstance: IModule | null = null; diff --git a/src/plugins/dashboard/public/application/legacy_app.js b/src/plugins/dashboard/public/application/legacy_app.js index 2d99a2c6a2253..8b8fdcb7a76ac 100644 --- a/src/plugins/dashboard/public/application/legacy_app.js +++ b/src/plugins/dashboard/public/application/legacy_app.js @@ -242,9 +242,17 @@ export function initDashboardApp(app, deps) { }, }) .otherwise({ - template: '', - controller: function () { - deps.navigateToDefaultApp(); + resolveRedirectTo: function ($rootScope) { + const path = window.location.hash.substr(1); + deps.restorePreviousUrl(); + $rootScope.$applyAsync(() => { + const { navigated } = deps.navigateToLegacyKibanaUrl(path); + if (!navigated) { + deps.navigateToDefaultApp(); + } + }); + // prevent angular from completing the navigation + return new Promise(() => {}); }, }); }); diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 5e2cb60965396..041a02a251e8a 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -215,7 +215,13 @@ export class DashboardPlugin const placeholderFactory = new PlaceholderEmbeddableFactory(); embeddable.registerEmbeddableFactory(placeholderFactory.type, placeholderFactory); - const { appMounted, appUnMounted, stop: stopUrlTracker, getActiveUrl } = createKbnUrlTracker({ + const { + appMounted, + appUnMounted, + stop: stopUrlTracker, + getActiveUrl, + restorePreviousUrl, + } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/dashboards'), defaultSubUrl: `#${DashboardConstants.LANDING_PAGE_PATH}`, storageKey: `lastUrl:${core.http.basePath.get()}:dashboard`, @@ -260,7 +266,7 @@ export class DashboardPlugin navigation, share: shareStart, data: dataStart, - kibanaLegacy: { dashboardConfig, navigateToDefaultApp }, + kibanaLegacy: { dashboardConfig, navigateToDefaultApp, navigateToLegacyKibanaUrl }, savedObjects, } = pluginsStart; @@ -269,6 +275,7 @@ export class DashboardPlugin core: coreStart, dashboardConfig, navigateToDefaultApp, + navigateToLegacyKibanaUrl, navigation, share: shareStart, data: dataStart, @@ -277,7 +284,6 @@ export class DashboardPlugin chrome: coreStart.chrome, addBasePath: coreStart.http.basePath.prepend, uiSettings: coreStart.uiSettings, - config: kibanaLegacy.config, savedQueryService: dataStart.query.savedQueries, embeddable: embeddableStart, dashboardCapabilities: coreStart.application.capabilities.dashboard, @@ -289,6 +295,7 @@ export class DashboardPlugin usageCollection, scopedHistory: () => this.currentHistory!, savedObjects, + restorePreviousUrl, }; // make sure the index pattern list is up to date await dataStart.indexPatterns.clearCache(); @@ -305,6 +312,15 @@ export class DashboardPlugin initAngularBootstrap(); core.application.register(app); + kibanaLegacy.forwardApp( + DashboardConstants.DASHBOARDS_ID, + DashboardConstants.DASHBOARDS_ID, + (path) => { + const [, tail] = /(\?.*)/.exec(path) || []; + // carry over query if it exists + return `#/list${tail || ''}`; + } + ); kibanaLegacy.forwardApp( DashboardConstants.DASHBOARD_ID, DashboardConstants.DASHBOARDS_ID, @@ -322,15 +338,6 @@ export class DashboardPlugin return `#/view/${id}${tail || ''}`; } ); - kibanaLegacy.forwardApp( - DashboardConstants.DASHBOARDS_ID, - DashboardConstants.DASHBOARDS_ID, - (path) => { - const [, tail] = /(\?.*)/.exec(path) || []; - // carry over query if it exists - return `#/list${tail || ''}`; - } - ); if (home) { home.featureCatalogue.register({ diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index 666d99362ce80..cd39a965ae6fc 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -198,7 +198,7 @@ export class IndexPattern implements IIndexPattern { private updateFromElasticSearch(response: any, forceFieldRefresh: boolean = false) { if (!response.found) { - throw new SavedObjectNotFound(type, this.id, 'kibana#/management/kibana/indexPatterns'); + throw new SavedObjectNotFound(type, this.id, 'management/kibana/indexPatterns'); } _.forOwn(this.mapping, (fieldMapping: FieldMappingSpec, name: string | undefined) => { diff --git a/src/plugins/data/server/saved_objects/search.ts b/src/plugins/data/server/saved_objects/search.ts index cf53a31741928..437c83f67bf5d 100644 --- a/src/plugins/data/server/saved_objects/search.ts +++ b/src/plugins/data/server/saved_objects/search.ts @@ -36,7 +36,7 @@ export const searchSavedObjectType: SavedObjectsType = { }, getInAppUrl(obj) { return { - path: `/app/discover#/${encodeURIComponent(obj.id)}`, + path: `/app/discover#/view/${encodeURIComponent(obj.id)}`, uiCapabilitiesPath: 'discover.show', }; }, diff --git a/src/plugins/discover/public/application/_hacks.scss b/src/plugins/discover/public/application/_hacks.scss index baf27bb9f82da..9bbe9cd14fd91 100644 --- a/src/plugins/discover/public/application/_hacks.scss +++ b/src/plugins/discover/public/application/_hacks.scss @@ -2,5 +2,3 @@ .tab-discover { overflow: hidden; } - - diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 8ff5af1e3a767..225f918c9f406 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -114,7 +114,7 @@ app.config(($routeProvider) => { }; }, }; - $routeProvider.when('/:id?', { + const discoverRoute = { ...defaults, template: indexTemplate, reloadOnSearch: false, @@ -177,7 +177,10 @@ app.config(($routeProvider) => { }); }, }, - }); + }; + + $routeProvider.when('/view/:id?', discoverRoute); + $routeProvider.when('/', discoverRoute); }); app.directive('discoverApp', function () { @@ -415,7 +418,7 @@ function discoverController( testId: 'discoverOpenButton', run: () => { showOpenSearchPanel({ - makeUrl: (searchId) => `#/${encodeURIComponent(searchId)}`, + makeUrl: (searchId) => `#/view/${encodeURIComponent(searchId)}`, I18nContext: core.i18n.Context, }); }, @@ -747,7 +750,7 @@ function discoverController( }); if (savedSearch.id !== $route.current.params.id) { - history.push(`/${encodeURIComponent(savedSearch.id)}`); + history.push(`/view/${encodeURIComponent(savedSearch.id)}`); } else { // Update defaults so that "reload saved query" functions correctly setAppState(getStateDefaults()); @@ -926,7 +929,9 @@ function discoverController( }; $scope.resetQuery = function () { - history.push(`/${encodeURIComponent($route.current.params.id)}`); + history.push( + $route.current.params.id ? `/view/${encodeURIComponent($route.current.params.id)}` : '/' + ); $route.reload(); }; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts index b1e6d27d76656..2fb6fb1e3a307 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts @@ -144,8 +144,7 @@ export function createTableRowDirective($compile: ng.ICompileService, $httpParam cellTemplate({ timefield: true, formatted: _displayField(row, indexPattern.timeFieldName), - filterable: - mapping(indexPattern.timeFieldName).filterable && _.isFunction($scope.filter), + filterable: mapping(indexPattern.timeFieldName).filterable && $scope.filter, column: indexPattern.timeFieldName, }) ); @@ -156,7 +155,7 @@ export function createTableRowDirective($compile: ng.ICompileService, $httpParam $scope.flattenedRow[column] !== undefined && mapping(column) && mapping(column).filterable && - _.isFunction($scope.filter); + $scope.filter; newHtmls.push( cellTemplate({ diff --git a/src/plugins/discover/public/application/angular/index.ts b/src/plugins/discover/public/application/angular/index.ts index f3fd3fb6622ee..20a22d4ae634d 100644 --- a/src/plugins/discover/public/application/angular/index.ts +++ b/src/plugins/discover/public/application/angular/index.ts @@ -25,4 +25,5 @@ import './discover'; import './doc'; import './context'; import './doc_viewer'; +import './redirect'; import './directives'; diff --git a/src/legacy/core_plugins/kibana/public/index.ts b/src/plugins/discover/public/application/angular/redirect.ts similarity index 54% rename from src/legacy/core_plugins/kibana/public/index.ts rename to src/plugins/discover/public/application/angular/redirect.ts index 6b1b7f0d249ff..bfa2f07f852e9 100644 --- a/src/legacy/core_plugins/kibana/public/index.ts +++ b/src/plugins/discover/public/application/angular/redirect.ts @@ -16,8 +16,22 @@ * specific language governing permissions and limitations * under the License. */ +import { getAngularModule, getServices, getUrlTracker } from '../../kibana_services'; -export { - ProcessedImportResponse, - processImportResponse, -} from '../../../../plugins/saved_objects_management/public/lib'; // eslint-disable-line @kbn/eslint/no-restricted-paths +getAngularModule().config(($routeProvider: any) => { + $routeProvider.otherwise({ + resolveRedirectTo: ($rootScope: any) => { + const path = window.location.hash.substr(1); + getUrlTracker().restorePreviousUrl(); + $rootScope.$applyAsync(() => { + const { kibanaLegacy } = getServices(); + const { navigated } = kibanaLegacy.navigateToLegacyKibanaUrl(path); + if (!navigated) { + kibanaLegacy.navigateToDefaultApp(); + } + }); + // prevent angular from completing the navigation + return new Promise(() => {}); + }, + }); +}); diff --git a/src/plugins/discover/public/application/application.ts b/src/plugins/discover/public/application/application.ts index 8167d4f903195..b49cefd2fcb16 100644 --- a/src/plugins/discover/public/application/application.ts +++ b/src/plugins/discover/public/application/application.ts @@ -19,11 +19,14 @@ import './index.scss'; import angular from 'angular'; +import { getServices } from '../kibana_services'; /** * Here's where Discover's inner angular is mounted and rendered */ export async function renderApp(moduleName: string, element: HTMLElement) { + // do not wait for fontawesome + getServices().kibanaLegacy.loadFontAwesome(); await import('./angular'); const $injector = mountDiscoverApp(moduleName, element); return () => $injector.get('$rootScope').$destroy(); diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 6d3e0b55140ba..75c83e30d80ad 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -42,6 +42,7 @@ import { SavedObjectKibanaServices } from 'src/plugins/saved_objects/public'; import { DiscoverStartPlugins } from './plugin'; import { createSavedSearchesLoader, SavedSearch } from './saved_searches'; import { getHistory } from './kibana_services'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; export interface DiscoverServices { addBasePath: (path: string) => string; @@ -57,6 +58,7 @@ export interface DiscoverServices { inspector: InspectorPublicPluginStart; metadata: { branch: string }; share?: SharePluginStart; + kibanaLegacy: KibanaLegacyStart; timefilter: TimefilterContract; toastNotifications: ToastsStart; getSavedSearchById: (id: string) => Promise; @@ -97,6 +99,7 @@ export async function buildServices( branch: context.env.packageInfo.branch, }, share: plugins.share, + kibanaLegacy: plugins.kibanaLegacy, timefilter: plugins.data.query.timefilter.timefilter, toastNotifications: core.notifications.toasts, uiSettings: core.uiSettings, diff --git a/src/plugins/discover/public/kibana_services.ts b/src/plugins/discover/public/kibana_services.ts index bbd0357f41ed4..cca63cd880b60 100644 --- a/src/plugins/discover/public/kibana_services.ts +++ b/src/plugins/discover/public/kibana_services.ts @@ -53,6 +53,7 @@ export function setServices(newServices: any) { export const [getUrlTracker, setUrlTracker] = createGetterSetter<{ setTrackedUrl: (url: string) => void; + restorePreviousUrl: () => void; }>('urlTracker'); export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter( diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 091288e3e65aa..ba97efa55068d 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -36,7 +36,7 @@ import { ChartsPluginStart } from 'src/plugins/charts/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; import { SharePluginStart, SharePluginSetup, UrlGeneratorContract } from 'src/plugins/share/public'; import { VisualizationsStart, VisualizationsSetup } from 'src/plugins/visualizations/public'; -import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public'; +import { KibanaLegacySetup, KibanaLegacyStart } from 'src/plugins/kibana_legacy/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public'; import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public'; @@ -55,6 +55,7 @@ import { setServices, setScopedHistory, getScopedHistory, + getServices, } from './kibana_services'; import { createSavedSearchesLoader } from './saved_searches'; import { registerFeature } from './register_feature'; @@ -130,6 +131,7 @@ export interface DiscoverStartPlugins { charts: ChartsPluginStart; data: DataPublicPluginStart; share?: SharePluginStart; + kibanaLegacy: KibanaLegacyStart; inspector: InspectorPublicPluginStart; visualizations: VisualizationsStart; } @@ -195,6 +197,7 @@ export class DiscoverPlugin appUnMounted, stop: stopUrlTracker, setActiveUrl: setTrackedUrl, + restorePreviousUrl, } = createKbnUrlTracker({ // we pass getter here instead of plain `history`, // so history is lazily created (when app is mounted) @@ -220,7 +223,7 @@ export class DiscoverPlugin }, ], }); - setUrlTracker({ setTrackedUrl }); + setUrlTracker({ setTrackedUrl, restorePreviousUrl }); this.stopUrlTracking = () => { stopUrlTracker(); }; @@ -260,7 +263,19 @@ export class DiscoverPlugin }, }); - plugins.kibanaLegacy.forwardApp('discover', 'discover'); + plugins.kibanaLegacy.forwardApp('doc', 'discover', (path) => { + return `#${path}`; + }); + plugins.kibanaLegacy.forwardApp('context', 'discover', (path) => { + return `#${path}`; + }); + plugins.kibanaLegacy.forwardApp('discover', 'discover', (path) => { + const [, id, tail] = /discover\/([^\?]+)(.*)/.exec(path) || []; + if (!id) { + return `#${path.replace('/discover', '') || '/'}`; + } + return `#/view/${id}${tail || ''}`; + }); if (plugins.home) { registerFeature(plugins.home); @@ -356,6 +371,7 @@ export class DiscoverPlugin throw Error('Discover plugin getEmbeddableInjector: initializeServices is undefined'); } const { core, plugins } = await this.initializeServices(); + getServices().kibanaLegacy.loadFontAwesome(); const { getInnerAngularModuleEmbeddable } = await import('./get_inner_angular'); getInnerAngularModuleEmbeddable( embeddableAngularName, diff --git a/src/plugins/discover/public/saved_searches/_saved_search.ts b/src/plugins/discover/public/saved_searches/_saved_search.ts index 9eda4f6ce9d16..2b8574a8fa118 100644 --- a/src/plugins/discover/public/saved_searches/_saved_search.ts +++ b/src/plugins/discover/public/saved_searches/_saved_search.ts @@ -66,7 +66,7 @@ export function createSavedSearchClass(services: SavedObjectKibanaServices) { }); this.showInRecentlyAccessed = true; this.id = id; - this.getFullPath = () => `/app/discover#/${String(id)}`; + this.getFullPath = () => `/app/discover#/view/${String(id)}`; } } diff --git a/src/plugins/discover/public/saved_searches/saved_searches.ts b/src/plugins/discover/public/saved_searches/saved_searches.ts index 1d1d4f17742a2..09be10b137494 100644 --- a/src/plugins/discover/public/saved_searches/saved_searches.ts +++ b/src/plugins/discover/public/saved_searches/saved_searches.ts @@ -34,7 +34,7 @@ export function createSavedSearchesLoader(services: SavedObjectKibanaServices) { nouns: 'saved searches', }; - savedSearchLoader.urlFor = (id: string) => `#/${encodeURIComponent(id)}`; + savedSearchLoader.urlFor = (id: string) => (id ? `#/view/${encodeURIComponent(id)}` : '#/'); return savedSearchLoader; } diff --git a/src/plugins/kibana_legacy/public/font_awesome/font_awesome.scss b/src/plugins/kibana_legacy/public/font_awesome/font_awesome.scss new file mode 100644 index 0000000000000..876a920269c49 --- /dev/null +++ b/src/plugins/kibana_legacy/public/font_awesome/font_awesome.scss @@ -0,0 +1,23 @@ +@font-face { + font-family: 'FontAwesome'; + src: url('~font-awesome/fonts/fontawesome-webfont.eot?v=4.7.0'); + src: url('~font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), + url('~font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), + url('~font-awesome/fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), + url('~font-awesome/fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), + url('~font-awesome/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +@import "font-awesome/scss/variables"; +@import "font-awesome/scss/core"; +@import "font-awesome/scss/icons"; + +// new file icon +.#{$fa-css-prefix}-file-new-o:before { content: $fa-var-file-o; } +.#{$fa-css-prefix}-file-new-o:after { content: $fa-var-plus; position: relative; margin-left: -1.0em; font-size: 0.5em; } + +// alias for alert types - allows class="fa fa-{{alertType}}" +.fa-success:before { content: $fa-var-check; } +.fa-danger:before { content: $fa-var-exclamation-circle; } diff --git a/src/plugins/advanced_settings/public/_index.scss b/src/plugins/kibana_legacy/public/font_awesome/index.ts similarity index 95% rename from src/plugins/advanced_settings/public/_index.scss rename to src/plugins/kibana_legacy/public/font_awesome/index.ts index d13c37bff32d0..318d44a3abfef 100644 --- a/src/plugins/advanced_settings/public/_index.scss +++ b/src/plugins/kibana_legacy/public/font_awesome/index.ts @@ -17,4 +17,4 @@ * under the License. */ -@import './management_app/index'; +import './font_awesome.scss'; diff --git a/src/plugins/kibana_legacy/public/forward_app/forward_app.ts b/src/plugins/kibana_legacy/public/forward_app/forward_app.ts index a5bccfc7d725d..89018df1ca7e1 100644 --- a/src/plugins/kibana_legacy/public/forward_app/forward_app.ts +++ b/src/plugins/kibana_legacy/public/forward_app/forward_app.ts @@ -22,10 +22,8 @@ import { AppNavLinkStatus } from '../../../../core/public'; import { navigateToLegacyKibanaUrl } from './navigate_to_legacy_kibana_url'; import { ForwardDefinition } from '../plugin'; -export const createLegacyUrlForwardApp = ( - core: CoreSetup<{}, { getForwards: () => ForwardDefinition[] }> -): App => ({ - id: 'url_migrate', +export const createLegacyUrlForwardApp = (core: CoreSetup, forwards: ForwardDefinition[]): App => ({ + id: 'kibana', chromeless: true, title: 'Legacy URL migration', navLinkStatus: AppNavLinkStatus.hidden, @@ -33,7 +31,7 @@ export const createLegacyUrlForwardApp = ( const hash = params.history.location.hash.substr(1); if (!hash) { - throw new Error('Could not forward URL'); + core.fatalErrors.add('Could not forward URL'); } const [ @@ -41,11 +39,13 @@ export const createLegacyUrlForwardApp = ( application, http: { basePath }, }, - , - { getForwards }, ] = await core.getStartServices(); - navigateToLegacyKibanaUrl(hash, getForwards(), basePath, application, window.location); + const result = await navigateToLegacyKibanaUrl(hash, forwards, basePath, application); + + if (!result.navigated) { + core.fatalErrors.add('Could not forward URL'); + } return () => {}; }, diff --git a/src/plugins/kibana_legacy/public/forward_app/navigate_to_legacy_kibana_url.test.ts b/src/plugins/kibana_legacy/public/forward_app/navigate_to_legacy_kibana_url.test.ts index de8fa9de7241e..30583aa95fc8c 100644 --- a/src/plugins/kibana_legacy/public/forward_app/navigate_to_legacy_kibana_url.test.ts +++ b/src/plugins/kibana_legacy/public/forward_app/navigate_to_legacy_kibana_url.test.ts @@ -25,7 +25,6 @@ import { coreMock } from '../../../../core/public/mocks'; describe('migrate legacy kibana urls', () => { let forwardDefinitions: ForwardDefinition[]; let coreStart: CoreStart; - let locationMock: Location; beforeEach(() => { coreStart = coreMock.createStart({ basePath: '/base/path' }); @@ -36,34 +35,33 @@ describe('migrate legacy kibana urls', () => { rewritePath: jest.fn(() => '/new/path'), }, ]; - locationMock = { href: '' } as Location; }); - it('should redirect to kibana if no forward definition is found', () => { - navigateToLegacyKibanaUrl( + it('should do nothing if no forward definition is found', () => { + const result = navigateToLegacyKibanaUrl( '/myOtherApp/deep/path', forwardDefinitions, coreStart.http.basePath, - coreStart.application, - locationMock + coreStart.application ); - expect(locationMock.href).toEqual('/base/path/app/kibana#/myOtherApp/deep/path'); + expect(result).toEqual({ navigated: false }); + expect(coreStart.application.navigateToApp).not.toHaveBeenCalled(); }); it('should call navigateToApp with migrated URL', () => { - navigateToLegacyKibanaUrl( + const result = navigateToLegacyKibanaUrl( '/myApp/deep/path', forwardDefinitions, coreStart.http.basePath, - coreStart.application, - locationMock + coreStart.application ); expect(coreStart.application.navigateToApp).toHaveBeenCalledWith('updatedApp', { path: '/new/path', + replace: true, }); expect(forwardDefinitions[0].rewritePath).toHaveBeenCalledWith('/myApp/deep/path'); - expect(locationMock.href).toEqual(''); + expect(result).toEqual({ navigated: true }); }); }); diff --git a/src/plugins/kibana_legacy/public/forward_app/navigate_to_legacy_kibana_url.ts b/src/plugins/kibana_legacy/public/forward_app/navigate_to_legacy_kibana_url.ts index a6aee351fde52..1df991f66747c 100644 --- a/src/plugins/kibana_legacy/public/forward_app/navigate_to_legacy_kibana_url.ts +++ b/src/plugins/kibana_legacy/public/forward_app/navigate_to_legacy_kibana_url.ts @@ -19,30 +19,26 @@ import { ApplicationStart, IBasePath } from 'kibana/public'; import { ForwardDefinition } from '../index'; +import { normalizePath } from '../utils/normalize_path'; export const navigateToLegacyKibanaUrl = ( path: string, forwards: ForwardDefinition[], basePath: IBasePath, - application: ApplicationStart, - location: Location -) => { - // navigate to the respective path in the legacy kibana plugin by default (for unmigrated plugins) - let targetAppId = 'kibana'; - let targetAppPath = path; + application: ApplicationStart +): { navigated: boolean } => { + const normalizedPath = normalizePath(path); // try to find an existing redirect for the target path if possible // this avoids having to load the legacy app just to get redirected to a core application again afterwards - const relevantForward = forwards.find((forward) => path.startsWith(`/${forward.legacyAppId}`)); - if (relevantForward) { - targetAppPath = relevantForward.rewritePath(path); - targetAppId = relevantForward.newAppId; - } - - if (targetAppId === 'kibana') { - // exception for kibana app because redirect won't work right otherwise - location.href = basePath.prepend(`/app/kibana#${targetAppPath}`); - } else { - application.navigateToApp(targetAppId, { path: targetAppPath }); + const relevantForward = forwards.find((forward) => + normalizedPath.startsWith(`/${forward.legacyAppId}`) + ); + if (!relevantForward) { + return { navigated: false }; } + const targetAppPath = relevantForward.rewritePath(normalizedPath); + const targetAppId = relevantForward.newAppId; + application.navigateToApp(targetAppId, { path: targetAppPath, replace: true }); + return { navigated: true }; }; diff --git a/src/plugins/kibana_legacy/public/mocks.ts b/src/plugins/kibana_legacy/public/mocks.ts index 5bdc76d44e4bf..a3cdb2106523c 100644 --- a/src/plugins/kibana_legacy/public/mocks.ts +++ b/src/plugins/kibana_legacy/public/mocks.ts @@ -17,7 +17,6 @@ * under the License. */ -import { EnvironmentMode, PackageInfo } from 'kibana/server'; import { KibanaLegacyPlugin } from './plugin'; export type Setup = jest.Mocked>; @@ -25,20 +24,9 @@ export type Start = jest.Mocked>; const createSetupContract = (): Setup => ({ forwardApp: jest.fn(), - registerLegacyAppAlias: jest.fn(), - registerLegacyApp: jest.fn(), - config: { - defaultAppId: 'home', - }, - env: {} as { - mode: Readonly; - packageInfo: Readonly; - }, }); const createStartContract = (): Start => ({ - getApps: jest.fn(), - getLegacyAppAliases: jest.fn(), getForwards: jest.fn(), config: { defaultAppId: 'home', @@ -48,6 +36,8 @@ const createStartContract = (): Start => ({ getHideWriteControls: jest.fn(), }, navigateToDefaultApp: jest.fn(), + navigateToLegacyKibanaUrl: jest.fn(), + loadFontAwesome: jest.fn(), }); export const kibanaLegacyPluginMock = { diff --git a/src/plugins/kibana_legacy/public/navigate_to_default_app.ts b/src/plugins/kibana_legacy/public/navigate_to_default_app.ts index 80b8343d3b229..cea901e9ba6b4 100644 --- a/src/plugins/kibana_legacy/public/navigate_to_default_app.ts +++ b/src/plugins/kibana_legacy/public/navigate_to_default_app.ts @@ -43,12 +43,7 @@ export function navigateToDefaultApp( // when the correct app is already loaded, just set the hash to the right value // otherwise use navigateToApp (or setting href in case of kibana app) if (currentAppId !== targetAppId) { - if (targetAppId === 'kibana') { - // exception for kibana app because redirect won't work right otherwise - window.location.href = basePath.prepend(`/app/kibana${targetAppPath}`); - } else { - application.navigateToApp(targetAppId, { path: targetAppPath }); - } + application.navigateToApp(targetAppId, { path: targetAppPath, replace: true }); } else if (overwriteHash) { window.location.hash = targetAppPath; } diff --git a/src/plugins/kibana_legacy/public/plugin.ts b/src/plugins/kibana_legacy/public/plugin.ts index c1a93f180db6f..59ce88c07f4f4 100644 --- a/src/plugins/kibana_legacy/public/plugin.ts +++ b/src/plugins/kibana_legacy/public/plugin.ts @@ -17,26 +17,14 @@ * under the License. */ -import { - App, - AppBase, - PluginInitializerContext, - AppUpdatableFields, - CoreStart, - CoreSetup, -} from 'kibana/public'; -import { Observable, Subscription } from 'rxjs'; +import { PluginInitializerContext, CoreStart, CoreSetup } from 'kibana/public'; +import { Subscription } from 'rxjs'; import { ConfigSchema } from '../config'; import { getDashboardConfig } from './dashboard_config'; import { navigateToDefaultApp } from './navigate_to_default_app'; import { createLegacyUrlForwardApp } from './forward_app'; import { injectHeaderStyle } from './utils/inject_header_style'; - -interface LegacyAppAliasDefinition { - legacyAppId: string; - newAppId: string; - keepPrefix: boolean; -} +import { navigateToLegacyKibanaUrl } from './forward_app/navigate_to_legacy_kibana_url'; export interface ForwardDefinition { legacyAppId: string; @@ -44,27 +32,7 @@ export interface ForwardDefinition { rewritePath: (legacyPath: string) => string; } -export type AngularRenderedAppUpdater = ( - app: AppBase -) => Partial | undefined; - -export interface AngularRenderedApp extends App { - /** - * Angular rendered apps are able to update the active url in the nav link (which is currently not - * possible for actual NP apps). When regular applications have the same functionality, this type - * override can be removed. - */ - updater$?: Observable; - /** - * If the active url is updated via the updater$ subject, the app id is assumed to be identical with - * the nav link id. If this is not the case, it is possible to provide another nav link id here. - */ - navLinkId?: string; -} - export class KibanaLegacyPlugin { - private apps: AngularRenderedApp[] = []; - private legacyAppAliases: LegacyAppAliasDefinition[] = []; private forwardDefinitions: ForwardDefinition[] = []; private currentAppId: string | undefined; private currentAppIdSubscription: Subscription | undefined; @@ -72,57 +40,8 @@ export class KibanaLegacyPlugin { constructor(private readonly initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup<{}, KibanaLegacyStart>) { - core.application.register(createLegacyUrlForwardApp(core)); + core.application.register(createLegacyUrlForwardApp(core, this.forwardDefinitions)); return { - /** - * @deprecated - * Register an app to be managed by the application service. - * This method works exactly as `core.application.register`. - * - * When an app is mounted, it is responsible for routing. The app - * won't be mounted again if the route changes within the prefix - * of the app (its id). It is fine to use whatever means for handling - * routing within the app. - * - * When switching to a URL outside of the current prefix, the app router - * shouldn't do anything because it doesn't own the routing anymore - - * the local application service takes over routing again, - * unmounts the current app and mounts the next app. - * - * @param app The app descriptor - */ - registerLegacyApp: (app: AngularRenderedApp) => { - this.apps.push(app); - }, - - /** - * @deprecated - * Forwards every URL starting with `legacyAppId` to the same URL starting - * with `newAppId` - e.g. `/legacy/my/legacy/path?q=123` gets forwarded to - * `/newApp/my/legacy/path?q=123`. - * - * When setting the `keepPrefix` option, the new app id is simply prepended. - * The example above would become `/newApp/legacy/my/legacy/path?q=123`. - * - * This method can be used to provide backwards compatibility for URLs when - * renaming or nesting plugins. For route changes after the prefix, please - * use the routing mechanism of your app. - * - * This method just redirects URLs within the legacy `kibana` app. - * - * @param legacyAppId The name of the old app to forward URLs from - * @param newAppId The name of the new app that handles the URLs now - * @param options Whether the prefix of the old app is kept to nest the legacy - * path into the new path - */ - registerLegacyAppAlias: ( - legacyAppId: string, - newAppId: string, - options: { keepPrefix: boolean } = { keepPrefix: false } - ) => { - this.legacyAppAliases.push({ legacyAppId, newAppId, ...options }); - }, - /** * Forwards URLs within the legacy `kibana` app to a new platform application. * @@ -164,18 +83,6 @@ export class KibanaLegacyPlugin { rewritePath: rewritePath || ((path) => `#${path.replace(`/${legacyAppId}`, '') || '/'}`), }); }, - - /** - * @deprecated - * The `defaultAppId` config key is temporarily exposed to be used in the legacy platform. - * As this setting is going away, no new code should depend on it. - */ - config: this.initializerContext.config.get(), - /** - * @deprecated - * Temporarily exposing the NP env to simulate initializer contexts in the LP. - */ - env: this.initializerContext.env, }; } @@ -186,21 +93,9 @@ export class KibanaLegacyPlugin { injectHeaderStyle(uiSettings); return { /** + * Used to power dashboard mode. Should be removed when dashboard mode is removed eventually. * @deprecated - * Just exported for wiring up with legacy platform, should not be used. - */ - getApps: () => this.apps, - /** - * @deprecated - * Just exported for wiring up with legacy platform, should not be used. - */ - getLegacyAppAliases: () => this.legacyAppAliases, - /** - * @deprecated - * Just exported for wiring up with legacy platform, should not be used. */ - getForwards: () => this.forwardDefinitions, - config: this.initializerContext.config.get(), dashboardConfig: getDashboardConfig(!application.capabilities.dashboard.showWriteControls), /** * Navigates to the app defined as kibana.defaultAppId. @@ -218,6 +113,32 @@ export class KibanaLegacyPlugin { overwriteHash ); }, + /** + * Resolves the provided hash using the registered forwards and navigates to the target app. + * If a navigation happened, `{ navigated: true }` will be returned. + * If no matching forward is found, `{ navigated: false }` will be returned. + * @param hash + */ + navigateToLegacyKibanaUrl: (hash: string) => { + return navigateToLegacyKibanaUrl(hash, this.forwardDefinitions, basePath, application); + }, + /** + * Loads the font-awesome icon font. Should be removed once the last consumer has migrated to EUI + * @deprecated + */ + loadFontAwesome: async () => { + await import('./font_awesome'); + }, + /** + * @deprecated + * Just exported for wiring up with legacy platform, should not be used. + */ + getForwards: () => this.forwardDefinitions, + /** + * @deprecated + * Just exported for wiring up with dashboard mode, should not be used. + */ + config: this.initializerContext.config.get(), }; } diff --git a/src/plugins/kibana_legacy/public/utils/index.ts b/src/plugins/kibana_legacy/public/utils/index.ts index 339079d3ac352..e7dd55ec5582b 100644 --- a/src/plugins/kibana_legacy/public/utils/index.ts +++ b/src/plugins/kibana_legacy/public/utils/index.ts @@ -19,6 +19,7 @@ export * from './migrate_legacy_query'; export * from './system_api'; +export * from './normalize_path'; // @ts-ignore export { KbnAccessibleClickProvider } from './kbn_accessible_click'; // @ts-ignore diff --git a/src/legacy/core_plugins/kibana/inject_vars.js b/src/plugins/kibana_legacy/public/utils/normalize_path.ts similarity index 73% rename from src/legacy/core_plugins/kibana/inject_vars.js rename to src/plugins/kibana_legacy/public/utils/normalize_path.ts index c3b906ee842e3..ece6c89cb7cdd 100644 --- a/src/legacy/core_plugins/kibana/inject_vars.js +++ b/src/plugins/kibana_legacy/public/utils/normalize_path.ts @@ -17,11 +17,11 @@ * under the License. */ -export function injectVars(server) { - const serverConfig = server.config(); +import { normalize } from 'path'; - return { - autocompleteTerminateAfter: serverConfig.get('kibana.autocompleteTerminateAfter'), - autocompleteTimeout: serverConfig.get('kibana.autocompleteTimeout'), - }; +export function normalizePath(path: string) { + // resolve ../ within the path + const normalizedPath = normalize(path); + // strip any leading slashes and dots and replace with single leading slash + return normalizedPath.replace(/(\.?\.?\/?)*/, '/'); } diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts index d9ff3ef36abaf..ce00b2bf68d93 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts @@ -38,6 +38,10 @@ export interface KbnUrlTracker { stop: () => void; setActiveUrl: (newUrl: string) => void; getActiveUrl: () => string; + /** + * Resets internal state to the last active url, discarding the most recent change + */ + restorePreviousUrl: () => void; } /** @@ -122,6 +126,8 @@ export function createKbnUrlTracker({ }): KbnUrlTracker { const storageInstance = storage || sessionStorage; + // local state storing previous active url to make restore possible + let previousActiveUrl: string = ''; // local state storing current listeners and active url let activeUrl: string = ''; let unsubscribeURLHistory: UnregisterCallback | undefined; @@ -157,6 +163,7 @@ export function createKbnUrlTracker({ toastNotifications.addDanger(e.message); } + previousActiveUrl = activeUrl; activeUrl = getActiveSubUrl(urlWithStates || urlWithHashes); storageInstance.setItem(storageKey, activeUrl); } @@ -183,6 +190,7 @@ export function createKbnUrlTracker({ { useHash: false }, baseUrl + (activeUrl || defaultSubUrl) ); + previousActiveUrl = activeUrl; // remove baseUrl prefix (just storing the sub url part) activeUrl = getActiveSubUrl(updatedUrl); storageInstance.setItem(storageKey, activeUrl); @@ -198,6 +206,7 @@ export function createKbnUrlTracker({ const storedUrl = storageInstance.getItem(storageKey); if (storedUrl) { activeUrl = storedUrl; + previousActiveUrl = storedUrl; setNavLink(storedUrl); } @@ -217,5 +226,8 @@ export function createKbnUrlTracker({ getActiveUrl() { return activeUrl; }, + restorePreviousUrl() { + activeUrl = previousActiveUrl; + }, }; } diff --git a/src/plugins/maps_legacy/public/_index.scss b/src/plugins/maps_legacy/public/_index.scss deleted file mode 100644 index 28cf4289bb048..0000000000000 --- a/src/plugins/maps_legacy/public/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './map/index'; diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts index a7f5427909334..cbe8b9213d577 100644 --- a/src/plugins/maps_legacy/public/index.ts +++ b/src/plugins/maps_legacy/public/index.ts @@ -44,6 +44,8 @@ import { // @ts-ignore import { mapTooltipProvider } from './tooltip_provider'; +import './map/index.scss'; + export interface MapsLegacyConfigType { regionmap: any; emsTileLayerId: string; diff --git a/src/plugins/maps_legacy/public/map/_index.scss b/src/plugins/maps_legacy/public/map/index.scss similarity index 100% rename from src/plugins/maps_legacy/public/map/_index.scss rename to src/plugins/maps_legacy/public/map/index.scss diff --git a/src/plugins/region_map/kibana.json b/src/plugins/region_map/kibana.json index 3a6f64e92bcba..ac7e1f8659d66 100644 --- a/src/plugins/region_map/kibana.json +++ b/src/plugins/region_map/kibana.json @@ -9,6 +9,7 @@ "visualizations", "expressions", "mapsLegacy", + "kibanaLegacy", "data" ] } diff --git a/src/plugins/region_map/public/kibana_services.ts b/src/plugins/region_map/public/kibana_services.ts index 1ef58c69c5bef..8367325c7415b 100644 --- a/src/plugins/region_map/public/kibana_services.ts +++ b/src/plugins/region_map/public/kibana_services.ts @@ -20,6 +20,7 @@ import { NotificationsStart } from 'kibana/public'; import { createGetterSetter } from '../../kibana_utils/public'; import { DataPublicPluginStart } from '../../data/public'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; export const [getFormatService, setFormatService] = createGetterSetter< DataPublicPluginStart['fieldFormats'] @@ -28,3 +29,7 @@ export const [getFormatService, setFormatService] = createGetterSetter< export const [getNotifications, setNotifications] = createGetterSetter( 'Notifications' ); + +export const [getKibanaLegacy, setKibanaLegacy] = createGetterSetter( + 'KibanaLegacy' +); diff --git a/src/plugins/region_map/public/plugin.ts b/src/plugins/region_map/public/plugin.ts index 09a13fbe9774e..6b31de758a4ca 100644 --- a/src/plugins/region_map/public/plugin.ts +++ b/src/plugins/region_map/public/plugin.ts @@ -31,10 +31,11 @@ import { createRegionMapFn } from './region_map_fn'; // @ts-ignore import { createRegionMapTypeDefinition } from './region_map_type'; import { getBaseMapsVis, IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/public'; -import { setFormatService, setNotifications } from './kibana_services'; +import { setFormatService, setNotifications, setKibanaLegacy } from './kibana_services'; import { DataPublicPluginStart } from '../../data/public'; import { RegionMapsConfigType } from './index'; import { ConfigSchema } from '../../maps_legacy/config'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; /** @private */ interface RegionMapVisualizationDependencies { @@ -55,6 +56,7 @@ export interface RegionMapPluginSetupDependencies { export interface RegionMapPluginStartDependencies { data: DataPublicPluginStart; notifications: NotificationsStart; + kibanaLegacy: KibanaLegacyStart; } /** @internal */ @@ -107,8 +109,9 @@ export class RegionMapPlugin implements Plugin = (doc) => ({ - ...doc, - attributes: { - ...doc.attributes, - url: - typeof doc.attributes.url === 'string' && doc.attributes.url.startsWith('/app/kibana') - ? doc.attributes.url.replace('/app/kibana', '/app/url_migrate') - : doc.attributes.url, - }, -}); diff --git a/src/plugins/share/server/saved_objects/url.ts b/src/plugins/share/server/saved_objects/url.ts index 3103777179741..c76c21993a13f 100644 --- a/src/plugins/share/server/saved_objects/url.ts +++ b/src/plugins/share/server/saved_objects/url.ts @@ -16,9 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { SavedObjectMigrationFn, SavedObjectsType } from 'kibana/server'; -import { flow } from 'lodash'; -import { migrateLegacyKibanaAppShortUrls } from './kibana_app_migration'; +import { SavedObjectsType } from 'kibana/server'; export const url: SavedObjectsType = { name: 'url', @@ -32,9 +30,6 @@ export const url: SavedObjectsType = { return `/goto/${encodeURIComponent(obj.id)}`; }, }, - migrations: { - '7.9.0': flow(migrateLegacyKibanaAppShortUrls), - }, mappings: { properties: { accessCount: { diff --git a/src/plugins/tile_map/kibana.json b/src/plugins/tile_map/kibana.json index 71ae0bb29d17f..bb8ef5a246549 100644 --- a/src/plugins/tile_map/kibana.json +++ b/src/plugins/tile_map/kibana.json @@ -9,6 +9,7 @@ "visualizations", "expressions", "mapsLegacy", + "kibanaLegacy", "data" ] } diff --git a/src/plugins/tile_map/public/plugin.ts b/src/plugins/tile_map/public/plugin.ts index e55f7189929df..20a45c586074a 100644 --- a/src/plugins/tile_map/public/plugin.ts +++ b/src/plugins/tile_map/public/plugin.ts @@ -35,6 +35,8 @@ import { createTileMapTypeDefinition } from './tile_map_type'; import { getBaseMapsVis, MapsLegacyPluginSetup } from '../../maps_legacy/public'; import { DataPublicPluginStart } from '../../data/public'; import { setFormatService, setQueryService } from './services'; +import { setKibanaLegacy } from './services'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; export interface TileMapConfigType { tilemap: any; @@ -58,6 +60,7 @@ export interface TileMapPluginSetupDependencies { /** @internal */ export interface TileMapPluginStartDependencies { data: DataPublicPluginStart; + kibanaLegacy: KibanaLegacyStart; } export interface TileMapPluginSetup { @@ -96,9 +99,10 @@ export class TileMapPlugin implements Plugin('Query'); + +export const [getKibanaLegacy, setKibanaLegacy] = createGetterSetter( + 'KibanaLegacy' +); diff --git a/src/plugins/tile_map/public/tile_map_visualization.js b/src/plugins/tile_map/public/tile_map_visualization.js index 1f4e5f09a9aa4..2ebb76d05c219 100644 --- a/src/plugins/tile_map/public/tile_map_visualization.js +++ b/src/plugins/tile_map/public/tile_map_visualization.js @@ -19,7 +19,7 @@ import { get } from 'lodash'; import { GeohashLayer } from './geohash_layer'; -import { getFormatService, getQueryService } from './services'; +import { getFormatService, getQueryService, getKibanaLegacy } from './services'; import { scaleBounds, geoContains, mapTooltipProvider } from '../../maps_legacy/public'; import { tooltipFormatter } from './tooltip_formatter'; @@ -60,6 +60,11 @@ export const createTileMapVisualization = (dependencies) => { this.vis.eventsSubject.next(updateVarsObject); }; + async render(esResponse, visParams) { + getKibanaLegacy().loadFontAwesome(); + await super.render(esResponse, visParams); + } + async _makeKibanaMap() { await super._makeKibanaMap(); diff --git a/src/plugins/vis_type_table/kibana.json b/src/plugins/vis_type_table/kibana.json index bb0f6478a4240..ed098d7161403 100644 --- a/src/plugins/vis_type_table/kibana.json +++ b/src/plugins/vis_type_table/kibana.json @@ -6,6 +6,7 @@ "requiredPlugins": [ "expressions", "visualizations", - "data" + "data", + "kibanaLegacy" ] } diff --git a/src/plugins/vis_type_table/public/plugin.ts b/src/plugins/vis_type_table/public/plugin.ts index a41d939523bcc..28f823df79d91 100644 --- a/src/plugins/vis_type_table/public/plugin.ts +++ b/src/plugins/vis_type_table/public/plugin.ts @@ -23,7 +23,8 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { createTableVisFn } from './table_vis_fn'; import { getTableVisTypeDefinition } from './table_vis_type'; import { DataPublicPluginStart } from '../../data/public'; -import { setFormatService } from './services'; +import { setFormatService, setKibanaLegacy } from './services'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; /** @internal */ export interface TablePluginSetupDependencies { @@ -34,6 +35,7 @@ export interface TablePluginSetupDependencies { /** @internal */ export interface TablePluginStartDependencies { data: DataPublicPluginStart; + kibanaLegacy: KibanaLegacyStart; } /** @internal */ @@ -55,7 +57,8 @@ export class TableVisPlugin implements Plugin, void> { ); } - public start(core: CoreStart, { data }: TablePluginStartDependencies) { + public start(core: CoreStart, { data, kibanaLegacy }: TablePluginStartDependencies) { setFormatService(data.fieldFormats); + setKibanaLegacy(kibanaLegacy); } } diff --git a/src/plugins/vis_type_table/public/services.ts b/src/plugins/vis_type_table/public/services.ts index 3aaffe75e27f1..b4f996f078f6b 100644 --- a/src/plugins/vis_type_table/public/services.ts +++ b/src/plugins/vis_type_table/public/services.ts @@ -19,7 +19,12 @@ import { createGetterSetter } from '../../kibana_utils/public'; import { DataPublicPluginStart } from '../../data/public'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; export const [getFormatService, setFormatService] = createGetterSetter< DataPublicPluginStart['fieldFormats'] >('table data.fieldFormats'); + +export const [getKibanaLegacy, setKibanaLegacy] = createGetterSetter( + 'table kibanaLegacy' +); diff --git a/src/plugins/vis_type_table/public/vis_controller.ts b/src/plugins/vis_type_table/public/vis_controller.ts index d49dd32c8c89c..a5086e0c9a2d8 100644 --- a/src/plugins/vis_type_table/public/vis_controller.ts +++ b/src/plugins/vis_type_table/public/vis_controller.ts @@ -22,6 +22,7 @@ import $ from 'jquery'; import { VisParams, ExprVis } from '../../visualizations/public'; import { getAngularModule } from './get_inner_angular'; +import { getKibanaLegacy } from './services'; import { initTableVisLegacyModule } from './table_vis_legacy_module'; const innerAngularName = 'kibana/table_vis'; @@ -64,6 +65,7 @@ export function getTableVisualizationControllerClass( } async render(esResponse: object, visParams: VisParams) { + getKibanaLegacy().loadFontAwesome(); await this.initLocalAngular(); return new Promise(async (resolve, reject) => { diff --git a/src/plugins/vis_type_vislib/kibana.json b/src/plugins/vis_type_vislib/kibana.json index 5b3088b399ebf..cad0ebe01494a 100644 --- a/src/plugins/vis_type_vislib/kibana.json +++ b/src/plugins/vis_type_vislib/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations"], + "requiredPlugins": ["charts", "data", "expressions", "visualizations", "kibanaLegacy"], "optionalPlugins": ["visTypeXy"] } diff --git a/src/plugins/vis_type_vislib/public/plugin.ts b/src/plugins/vis_type_vislib/public/plugin.ts index 19bbf89ee0243..c6a6b6f82592b 100644 --- a/src/plugins/vis_type_vislib/public/plugin.ts +++ b/src/plugins/vis_type_vislib/public/plugin.ts @@ -44,7 +44,8 @@ import { } from './vis_type_vislib_vis_types'; import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; -import { setFormatService, setDataActions } from './services'; +import { setFormatService, setDataActions, setKibanaLegacy } from './services'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; export interface VisTypeVislibDependencies { uiSettings: IUiSettingsClient; @@ -62,6 +63,7 @@ export interface VisTypeVislibPluginSetupDependencies { /** @internal */ export interface VisTypeVislibPluginStartDependencies { data: DataPublicPluginStart; + kibanaLegacy: KibanaLegacyStart; } type VisTypeVislibCoreSetup = CoreSetup; @@ -109,8 +111,9 @@ export class VisTypeVislibPlugin implements Plugin { ); } - public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) { + public start(core: CoreStart, { data, kibanaLegacy }: VisTypeVislibPluginStartDependencies) { setFormatService(data.fieldFormats); setDataActions(data.actions); + setKibanaLegacy(kibanaLegacy); } } diff --git a/src/plugins/vis_type_vislib/public/services.ts b/src/plugins/vis_type_vislib/public/services.ts index 633fae9c7f2a6..7257b98f2e9f5 100644 --- a/src/plugins/vis_type_vislib/public/services.ts +++ b/src/plugins/vis_type_vislib/public/services.ts @@ -19,6 +19,7 @@ import { createGetterSetter } from '../../kibana_utils/public'; import { DataPublicPluginStart } from '../../data/public'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; export const [getDataActions, setDataActions] = createGetterSetter< DataPublicPluginStart['actions'] @@ -27,3 +28,7 @@ export const [getDataActions, setDataActions] = createGetterSetter< export const [getFormatService, setFormatService] = createGetterSetter< DataPublicPluginStart['fieldFormats'] >('vislib data.fieldFormats'); + +export const [getKibanaLegacy, setKibanaLegacy] = createGetterSetter( + 'vislib kibanalegacy' +); diff --git a/src/plugins/vis_type_vislib/public/vis_controller.tsx b/src/plugins/vis_type_vislib/public/vis_controller.tsx index 262290b071abc..86ef98de045d7 100644 --- a/src/plugins/vis_type_vislib/public/vis_controller.tsx +++ b/src/plugins/vis_type_vislib/public/vis_controller.tsx @@ -27,6 +27,7 @@ import { VisTypeVislibDependencies } from './plugin'; import { mountReactNode } from '../../../core/public/utils'; import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; import { VisParams, ExprVis } from '../../visualizations/public'; +import { getKibanaLegacy } from './services'; const legendClassName = { top: 'visLib--legend-top', @@ -72,6 +73,8 @@ export const createVislibVisController = (deps: VisTypeVislibDependencies) => { this.destroy(); } + getKibanaLegacy().loadFontAwesome(); + return new Promise(async (resolve) => { if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { return resolve(); diff --git a/src/plugins/visualize/public/application/legacy_app.js b/src/plugins/visualize/public/application/legacy_app.js index 42e8b07ee6310..452118f8097da 100644 --- a/src/plugins/visualize/public/application/legacy_app.js +++ b/src/plugins/visualize/public/application/legacy_app.js @@ -244,9 +244,17 @@ export function initVisualizeApp(app, deps) { }, }) .otherwise({ - template: '', - controller: function () { - deps.kibanaLegacy.navigateToDefaultApp(); + resolveRedirectTo: function ($rootScope) { + const path = window.location.hash.substr(1); + deps.restorePreviousUrl(); + $rootScope.$applyAsync(() => { + const { navigated } = deps.kibanaLegacy.navigateToLegacyKibanaUrl(path); + if (!navigated) { + deps.kibanaLegacy.navigateToDefaultApp(); + } + }); + // prevent angular from completing the navigation + return new Promise(() => {}); }, }); }); diff --git a/src/plugins/visualize/public/kibana_services.ts b/src/plugins/visualize/public/kibana_services.ts index d954a3f4925ac..f1e6b0b37d55b 100644 --- a/src/plugins/visualize/public/kibana_services.ts +++ b/src/plugins/visualize/public/kibana_services.ts @@ -55,6 +55,7 @@ export interface VisualizeKibanaServices { embeddable: EmbeddableStart; I18nContext: I18nStart['Context']; setActiveUrl: (newUrl: string) => void; + restorePreviousUrl: () => void; createVisEmbeddableFromObject: VisualizationsStart['__LEGACY']['createVisEmbeddableFromObject']; scopedHistory: () => ScopedHistory; savedObjects: SavedObjectsStart; diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index 3bd111084e34b..bec082642d3d4 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -73,7 +73,13 @@ export class VisualizePlugin core: CoreSetup, { home, kibanaLegacy, data }: VisualizePluginSetupDependencies ) { - const { appMounted, appUnMounted, stop: stopUrlTracker, setActiveUrl } = createKbnUrlTracker({ + const { + appMounted, + appUnMounted, + stop: stopUrlTracker, + setActiveUrl, + restorePreviousUrl, + } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/visualize'), defaultSubUrl: '#/', storageKey: `lastUrl:${core.http.basePath.get()}:visualize`, @@ -136,6 +142,7 @@ export class VisualizePlugin pluginsStart.visualizations.__LEGACY.createVisEmbeddableFromObject, scopedHistory: () => this.currentHistory!, savedObjects: pluginsStart.savedObjects, + restorePreviousUrl, }; setServices(deps); diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index 4d9f1c1658139..235d789a388df 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -221,7 +221,7 @@ export default function ({ getService }: FtrProviderContext) { editUrl: '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { - path: '/app/discover#/960372e0-3224-11e8-a572-ffca06da1357', + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', }, }); diff --git a/test/api_integration/apis/saved_objects_management/relationships.ts b/test/api_integration/apis/saved_objects_management/relationships.ts index 1db4df181e0e9..225fc5456e743 100644 --- a/test/api_integration/apis/saved_objects_management/relationships.ts +++ b/test/api_integration/apis/saved_objects_management/relationships.ts @@ -283,7 +283,7 @@ export default function ({ getService }: FtrProviderContext) { editUrl: '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { - path: '/app/discover#/960372e0-3224-11e8-a572-ffca06da1357', + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', }, }, @@ -323,7 +323,7 @@ export default function ({ getService }: FtrProviderContext) { editUrl: '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { - path: '/app/discover#/960372e0-3224-11e8-a572-ffca06da1357', + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', }, }, @@ -366,7 +366,7 @@ export default function ({ getService }: FtrProviderContext) { editUrl: '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { - path: '/app/discover#/960372e0-3224-11e8-a572-ffca06da1357', + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', }, }, @@ -406,7 +406,7 @@ export default function ({ getService }: FtrProviderContext) { editUrl: '/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357', inAppUrl: { - path: '/app/discover#/960372e0-3224-11e8-a572-ffca06da1357', + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'discover.show', }, }, diff --git a/test/functional/apps/bundles/index.js b/test/functional/apps/bundles/index.js index ead6412564751..f106ea895c2e6 100644 --- a/test/functional/apps/bundles/index.js +++ b/test/functional/apps/bundles/index.js @@ -65,7 +65,7 @@ export default function ({ getService }) { it('returns gzip files when no brotli version exists', () => supertest - .get(`/${buildNum}/bundles/commons.style.css`) // legacy optimizer does not create brotli outputs + .get(`/${buildNum}/bundles/light_theme.style.css`) // legacy optimizer does not create brotli outputs .set('Accept-Encoding', 'gzip, br') .expect(200) .expect('Content-Encoding', 'gzip')); diff --git a/test/functional/apps/dashboard/index.js b/test/functional/apps/dashboard/index.js index a8d0e03c9421e..1e310c1ddd268 100644 --- a/test/functional/apps/dashboard/index.js +++ b/test/functional/apps/dashboard/index.js @@ -58,6 +58,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./embed_mode')); loadTestFile(require.resolve('./dashboard_back_button')); loadTestFile(require.resolve('./dashboard_error_handling')); + loadTestFile(require.resolve('./legacy_urls')); // Note: This one must be last because it unloads some data for one of its tests! // No, this isn't ideal, but loading/unloading takes so much time and these are all bunched diff --git a/test/functional/apps/dashboard/legacy_urls.ts b/test/functional/apps/dashboard/legacy_urls.ts new file mode 100644 index 0000000000000..e606649c1df9f --- /dev/null +++ b/test/functional/apps/dashboard/legacy_urls.ts @@ -0,0 +1,111 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects([ + 'dashboard', + 'header', + 'common', + 'timePicker', + 'visualize', + 'visEditor', + ]); + const pieChart = getService('pieChart'); + const browser = getService('browser'); + const find = getService('find'); + const log = getService('log'); + const dashboardAddPanel = getService('dashboardAddPanel'); + const listingTable = getService('listingTable'); + const esArchiver = getService('esArchiver'); + + let kibanaLegacyBaseUrl: string; + let kibanaVisualizeBaseUrl: string; + let testDashboardId: string; + + describe('legacy urls', function describeIndexTests() { + before(async function () { + await esArchiver.load('dashboard/current/kibana'); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie'); + await PageObjects.dashboard.saveDashboard('legacyTest', { waitDialogIsClosed: true }); + await PageObjects.header.waitUntilLoadingHasFinished(); + const currentUrl = await browser.getCurrentUrl(); + await log.debug(`Current url is ${currentUrl}`); + testDashboardId = /#\/view\/(.+)\?/.exec(currentUrl)![1]; + kibanaLegacyBaseUrl = + currentUrl.substring(0, currentUrl.indexOf('/app/dashboards')) + '/app/kibana'; + kibanaVisualizeBaseUrl = + currentUrl.substring(0, currentUrl.indexOf('/app/dashboards')) + '/app/visualize'; + await log.debug(`id is ${testDashboardId}`); + }); + + after(async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); + await listingTable.deleteItem('legacyTest', testDashboardId); + }); + + describe('kibana link redirect', () => { + it('redirects from old kibana app URL', async () => { + const url = `${kibanaLegacyBaseUrl}#/dashboard/${testDashboardId}`; + await browser.get(url, true); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.timePicker.setDefaultDataRange(); + + await PageObjects.dashboard.waitForRenderComplete(); + await pieChart.expectPieSliceCount(5); + }); + + it('redirects from legacy hash in wrong app', async () => { + const url = `${kibanaVisualizeBaseUrl}#/dashboard/${testDashboardId}`; + await browser.get(url, true); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.timePicker.setDefaultDataRange(); + + await PageObjects.dashboard.waitForRenderComplete(); + await pieChart.expectPieSliceCount(5); + }); + + it('resolves markdown link', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickMarkdownWidget(); + await PageObjects.visEditor.setMarkdownTxt(`[abc](#/dashboard/${testDashboardId})`); + await PageObjects.visEditor.clickGo(); + (await find.byLinkText('abc')).click(); + + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.timePicker.setDefaultDataRange(); + + await PageObjects.dashboard.waitForRenderComplete(); + await pieChart.expectPieSliceCount(5); + }); + + it('back button works', async () => { + // back to default time range + await browser.goBack(); + // back to last app + await browser.goBack(); + await PageObjects.visEditor.expectMarkdownTextArea(); + await browser.goForward(); + }); + }); + }); +} diff --git a/test/functional/apps/discover/_shared_links.js b/test/functional/apps/discover/_shared_links.js index 38d8812fa3103..5c6a70450a0aa 100644 --- a/test/functional/apps/discover/_shared_links.js +++ b/test/functional/apps/discover/_shared_links.js @@ -109,7 +109,7 @@ export default function ({ getService, getPageObjects }) { const expectedUrl = baseUrl + '/app/discover#' + - '/ab12e3c0-f231-11e6-9486-733b1ac9221a' + + '/view/ab12e3c0-f231-11e6-9486-733b1ac9221a' + '?_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)' + "%2Ctime%3A(from%3A'2015-09-19T06%3A31%3A44.000Z'%2C" + "to%3A'2015-09-23T18%3A31%3A44.000Z'))"; diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index c4c7c2aaffabd..9fcb38efce0db 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -230,6 +230,10 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP await testSubjects.click('dropPartialBucketsCheckbox'); } + public async expectMarkdownTextArea() { + await testSubjects.existOrFail('markdownTextarea'); + } + public async setMarkdownTxt(markdownTxt: string) { const input = await testSubjects.find('markdownTextarea'); await input.clearValue(); diff --git a/x-pack/plugins/graph/kibana.json b/x-pack/plugins/graph/kibana.json index 4cae14f8939b2..ebe18dba2b58c 100644 --- a/x-pack/plugins/graph/kibana.json +++ b/x-pack/plugins/graph/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["licensing", "data", "navigation", "savedObjects"], + "requiredPlugins": ["licensing", "data", "navigation", "savedObjects", "kibanaLegacy"], "optionalPlugins": ["home", "features"], "configPath": ["xpack", "graph"] } diff --git a/x-pack/plugins/graph/public/application.ts b/x-pack/plugins/graph/public/application.ts index b46bc88500e0a..0969b80bc38b0 100644 --- a/x-pack/plugins/graph/public/application.ts +++ b/x-pack/plugins/graph/public/application.ts @@ -35,6 +35,7 @@ import { configureAppAngularModule, createTopNavDirective, createTopNavHelper, + KibanaLegacyStart, } from '../../../../src/plugins/kibana_legacy/public'; import './index.scss'; @@ -67,9 +68,11 @@ export interface GraphDependencies { graphSavePolicy: string; overlays: OverlayStart; savedObjects: SavedObjectsStart; + kibanaLegacy: KibanaLegacyStart; } -export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { +export const renderApp = ({ appBasePath, element, kibanaLegacy, ...deps }: GraphDependencies) => { + kibanaLegacy.loadFontAwesome(); const graphAngularModule = createLocalAngularModule(deps.navigation); configureAppAngularModule( graphAngularModule, diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index e97735c50388f..5b2566ffab7c0 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -10,7 +10,10 @@ import { AppMountParameters, Plugin } from 'src/core/public'; import { PluginInitializerContext } from 'kibana/public'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; -import { initAngularBootstrap } from '../../../../src/plugins/kibana_legacy/public'; +import { + initAngularBootstrap, + KibanaLegacyStart, +} from '../../../../src/plugins/kibana_legacy/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; @@ -34,6 +37,7 @@ export interface GraphPluginStartDependencies { navigation: NavigationStart; data: DataPublicPluginStart; savedObjects: SavedObjectsStart; + kibanaLegacy: KibanaLegacyStart; } export class GraphPlugin @@ -85,6 +89,7 @@ export class GraphPlugin core: coreStart, navigation: pluginsStart.navigation, data: pluginsStart.data, + kibanaLegacy: pluginsStart.kibanaLegacy, savedObjectsClient: coreStart.savedObjects.client, addBasePath: core.http.basePath.prepend, getBasePath: core.http.basePath.get, diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index e9d4aff3484b1..f93e7bc19f960 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -15,7 +15,8 @@ "usageCollection", "share", "embeddable", - "uiActions" + "uiActions", + "kibanaLegacy" ], "optionalPlugins": [ "security", diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index b871d857f7fde..3df176ff25cb4 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -78,6 +78,8 @@ export const renderApp = ( urlGenerators: deps.share.urlGenerators, }); + deps.kibanaLegacy.loadFontAwesome(); + const mlLicense = setLicenseCache(deps.licensing); appMountParams.onAppLeave((actions) => actions.default()); diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index be2ebb3caa416..7f7544a44efa7 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -30,10 +30,12 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { registerEmbeddables } from './embeddables'; import { UiActionsSetup } from '../../../../src/plugins/ui_actions/public'; import { registerMlUiActions } from './ui_actions'; +import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public'; export interface MlStartDependencies { data: DataPublicPluginStart; share: SharePluginStart; + kibanaLegacy: KibanaLegacyStart; } export interface MlSetupDependencies { security?: SecurityPluginSetup; @@ -70,6 +72,7 @@ export class MlPlugin implements Plugin { { data: pluginsStart.data, share: pluginsStart.share, + kibanaLegacy: pluginsStart.kibanaLegacy, security: pluginsSetup.security, licensing: pluginsSetup.licensing, management: pluginsSetup.management, diff --git a/x-pack/plugins/monitoring/public/angular/index.ts b/x-pack/plugins/monitoring/public/angular/index.ts index 3f2a51a898d1f..69d97a5e3bdc3 100644 --- a/x-pack/plugins/monitoring/public/angular/index.ts +++ b/x-pack/plugins/monitoring/public/angular/index.ts @@ -25,12 +25,22 @@ export class AngularApp { isCloud, pluginInitializerContext, externalConfig, + kibanaLegacy, } = deps; const app: IModule = localAppModule(deps); app.run(($injector: angular.auto.IInjectorService) => { this.injector = $injector; Legacy.init( - { core, element, data, navigation, isCloud, pluginInitializerContext, externalConfig }, + { + core, + element, + data, + navigation, + isCloud, + pluginInitializerContext, + externalConfig, + kibanaLegacy, + }, this.injector ); }); diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 24383028e558c..de8c8d59b78bf 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -70,6 +70,7 @@ export class MonitoringPlugin const { AngularApp } = await import('./angular'); const deps: MonitoringPluginDependencies = { navigation: pluginsStart.navigation, + kibanaLegacy: pluginsStart.kibanaLegacy, element: params.element, core: coreStart, data: pluginsStart.data, @@ -78,6 +79,7 @@ export class MonitoringPlugin externalConfig: this.getExternalConfig(), }; + pluginsStart.kibanaLegacy.loadFontAwesome(); this.setInitialTimefilter(deps); this.overrideAlertingEmailDefaults(deps); diff --git a/x-pack/plugins/monitoring/public/types.ts b/x-pack/plugins/monitoring/public/types.ts index b8c854f4e7ee0..6266755a04120 100644 --- a/x-pack/plugins/monitoring/public/types.ts +++ b/x-pack/plugins/monitoring/public/types.ts @@ -7,6 +7,7 @@ import { PluginInitializerContext, CoreStart } from 'kibana/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths export { MonitoringConfig } from '../server'; @@ -14,6 +15,7 @@ export { MonitoringConfig } from '../server'; export interface MonitoringPluginDependencies { navigation: NavigationStart; data: DataPublicPluginStart; + kibanaLegacy: KibanaLegacyStart; element: HTMLElement; core: CoreStart; isCloud: boolean; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx index c5fff36c797cc..47fc603ee46e8 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx @@ -22,10 +22,6 @@ import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ToastsStart } from 'src/core/public'; -import { - ProcessedImportResponse, - processImportResponse, -} from '../../../../../../src/legacy/core_plugins/kibana/public'; import { SavedObjectsManagementRecord } from '../../../../../../src/plugins/saved_objects_management/public'; import { Space } from '../../../common/model/space'; import { SpacesManager } from '../../spaces_manager'; @@ -33,6 +29,10 @@ import { ProcessingCopyToSpace } from './processing_copy_to_space'; import { CopyToSpaceFlyoutFooter } from './copy_to_space_flyout_footer'; import { CopyToSpaceForm } from './copy_to_space_form'; import { CopyOptions, ImportRetry } from '../types'; +import { + ProcessedImportResponse, + processImportResponse, +} from '../../../../../../src/plugins/saved_objects_management/public'; interface Props { onClose: () => void; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx index 38bd3e04bc240..d7ded819771fc 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx @@ -8,8 +8,8 @@ import React, { Fragment } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/kibana/public'; import { ImportRetry } from '../types'; +import { ProcessedImportResponse } from '../../../../../../src/plugins/saved_objects_management/public'; interface Props { copyInProgress: boolean; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx index fc1810ba8f819..255268d388eb8 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx @@ -13,8 +13,10 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/kibana/public'; -import { SavedObjectsManagementRecord } from '../../../../../../src/plugins/saved_objects_management/public'; +import { + ProcessedImportResponse, + SavedObjectsManagementRecord, +} from 'src/plugins/saved_objects_management/public'; import { Space } from '../../../common/model/space'; import { CopyOptions, ImportRetry } from '../types'; import { SpaceResult } from './space_result'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts index 65a0cabfeb716..a8ecd7c7b9d9f 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from 'src/legacy/core_plugins/kibana/public'; +import { ProcessedImportResponse } from 'src/plugins/saved_objects_management/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts index 5c1fe6afcf083..518e89df579a6 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ProcessedImportResponse } from 'src/legacy/core_plugins/kibana/public'; -import { SavedObjectsManagementRecord } from 'src/plugins/saved_objects_management/public'; +import { + SavedObjectsManagementRecord, + ProcessedImportResponse, +} from 'src/plugins/saved_objects_management/public'; export interface SummarizedSavedObjectResult { type: string; From 778136f3bf78391d1cae0fb631d01d6d53d10445 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 24 Jun 2020 12:42:54 +0300 Subject: [PATCH 34/36] expose DocLinks API from start only (#68745) * exose DocLinks API from start only * update docs * fix type errors * update docs Co-authored-by: Elastic Machine --- ...a-plugin-core-public.coresetup.doclinks.md | 13 ---------- .../kibana-plugin-core-public.coresetup.md | 1 - ...e-public.doclinkssetup.doc_link_version.md | 11 -------- ...ublic.doclinkssetup.elastic_website_url.md | 11 -------- ...kibana-plugin-core-public.doclinkssetup.md | 21 --------------- ...e-public.doclinksstart.doc_link_version.md | 11 ++++++++ ...ublic.doclinksstart.elastic_website_url.md | 11 ++++++++ ...plugin-core-public.doclinksstart.links.md} | 4 +-- ...kibana-plugin-core-public.doclinksstart.md | 13 ++++++++-- .../core/public/kibana-plugin-core-public.md | 3 +-- src/core/public/core_system.ts | 5 ++-- .../doc_links/doc_links_service.mock.ts | 12 ++++----- .../doc_links/doc_links_service.test.ts | 26 +++---------------- .../public/doc_links/doc_links_service.ts | 24 ++++------------- src/core/public/doc_links/index.ts | 2 +- src/core/public/index.ts | 5 +--- src/core/public/plugins/plugin_context.ts | 1 - .../public/plugins/plugins_service.test.ts | 1 - src/core/public/public.api.md | 9 ++----- .../component_templates_context.tsx | 4 +-- .../component_templates/lib/documentation.ts | 4 +-- .../components/expression_chart.test.tsx | 3 ++- 22 files changed, 62 insertions(+), 133 deletions(-) delete mode 100644 docs/development/core/public/kibana-plugin-core-public.coresetup.doclinks.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.doclinkssetup.doc_link_version.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.doclinkssetup.elastic_website_url.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.doclinkssetup.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.doclinksstart.doc_link_version.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.doclinksstart.elastic_website_url.md rename docs/development/core/public/{kibana-plugin-core-public.doclinkssetup.links.md => kibana-plugin-core-public.doclinksstart.links.md} (94%) diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.doclinks.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.doclinks.md deleted file mode 100644 index b239319c427fe..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.coresetup.doclinks.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [CoreSetup](./kibana-plugin-core-public.coresetup.md) > [docLinks](./kibana-plugin-core-public.coresetup.doclinks.md) - -## CoreSetup.docLinks property - -[DocLinksSetup](./kibana-plugin-core-public.doclinkssetup.md) - -Signature: - -```typescript -docLinks: DocLinksSetup; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.md index 4f981b5a40139..870fa33dce900 100644 --- a/docs/development/core/public/kibana-plugin-core-public.coresetup.md +++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.md @@ -18,7 +18,6 @@ export interface CoreSetupApplicationSetup | [ApplicationSetup](./kibana-plugin-core-public.applicationsetup.md) | | [context](./kibana-plugin-core-public.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-core-public.contextsetup.md) | -| [docLinks](./kibana-plugin-core-public.coresetup.doclinks.md) | DocLinksSetup | [DocLinksSetup](./kibana-plugin-core-public.doclinkssetup.md) | | [fatalErrors](./kibana-plugin-core-public.coresetup.fatalerrors.md) | FatalErrorsSetup | [FatalErrorsSetup](./kibana-plugin-core-public.fatalerrorssetup.md) | | [getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-public.startservicesaccessor.md) | | [http](./kibana-plugin-core-public.coresetup.http.md) | HttpSetup | [HttpSetup](./kibana-plugin-core-public.httpsetup.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.doc_link_version.md b/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.doc_link_version.md deleted file mode 100644 index c8d13bab92b05..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.doc_link_version.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DocLinksSetup](./kibana-plugin-core-public.doclinkssetup.md) > [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinkssetup.doc_link_version.md) - -## DocLinksSetup.DOC\_LINK\_VERSION property - -Signature: - -```typescript -readonly DOC_LINK_VERSION: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.elastic_website_url.md b/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.elastic_website_url.md deleted file mode 100644 index d8493148bae10..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.elastic_website_url.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DocLinksSetup](./kibana-plugin-core-public.doclinkssetup.md) > [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinkssetup.elastic_website_url.md) - -## DocLinksSetup.ELASTIC\_WEBSITE\_URL property - -Signature: - -```typescript -readonly ELASTIC_WEBSITE_URL: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.md b/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.md deleted file mode 100644 index 9e7938bd9c850..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DocLinksSetup](./kibana-plugin-core-public.doclinkssetup.md) - -## DocLinksSetup interface - - -Signature: - -```typescript -export interface DocLinksSetup -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinkssetup.doc_link_version.md) | string | | -| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinkssetup.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinkssetup.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
} | | - diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.doc_link_version.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.doc_link_version.md new file mode 100644 index 0000000000000..8140b3fcf380f --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.doc_link_version.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) > [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) + +## DocLinksStart.DOC\_LINK\_VERSION property + +Signature: + +```typescript +readonly DOC_LINK_VERSION: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.elastic_website_url.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.elastic_website_url.md new file mode 100644 index 0000000000000..af770ed3055aa --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.elastic_website_url.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) > [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) + +## DocLinksStart.ELASTIC\_WEBSITE\_URL property + +Signature: + +```typescript +readonly ELASTIC_WEBSITE_URL: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md similarity index 94% rename from docs/development/core/public/kibana-plugin-core-public.doclinkssetup.links.md rename to docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 80e2702451d86..a03b1b74fc1ac 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DocLinksSetup](./kibana-plugin-core-public.doclinkssetup.md) > [links](./kibana-plugin-core-public.doclinkssetup.links.md) +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) > [links](./kibana-plugin-core-public.doclinksstart.links.md) -## DocLinksSetup.links property +## DocLinksStart.links property Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index af2a41b691727..8f739950d249b 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -2,11 +2,20 @@ [Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) -## DocLinksStart type +## DocLinksStart interface Signature: ```typescript -export declare type DocLinksStart = DocLinksSetup; +export interface DocLinksStart ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | +| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
} | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index dda6b6ac0c60a..b0612ff4d5b65 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -65,7 +65,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ContextSetup](./kibana-plugin-core-public.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-core-public.coresetup.md) | Core services exposed to the Plugin setup lifecycle | | [CoreStart](./kibana-plugin-core-public.corestart.md) | Core services exposed to the Plugin start lifecycle | -| [DocLinksSetup](./kibana-plugin-core-public.doclinkssetup.md) | | +| [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | | | [EnvironmentMode](./kibana-plugin-core-public.environmentmode.md) | | | [ErrorToastOptions](./kibana-plugin-core-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) error APIs. | | [FatalErrorInfo](./kibana-plugin-core-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | @@ -157,7 +157,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-core-public.chromehelpextensionmenugithublink.md) | | | [ChromeHelpExtensionMenuLink](./kibana-plugin-core-public.chromehelpextensionmenulink.md) | | | [ChromeNavLinkUpdateableFields](./kibana-plugin-core-public.chromenavlinkupdateablefields.md) | | -| [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | | | [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | | [Freezable](./kibana-plugin-core-public.freezable.md) | | | [HandlerContextType](./kibana-plugin-core-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-core-public.handlerfunction.md) to represent the type of the context. | diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index d6172b77d3ca5..00fabc2b6f2f1 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -163,7 +163,7 @@ export class CoreSystem { i18n: this.i18n.getContext(), }); await this.integrations.setup(); - const docLinks = this.docLinks.setup({ injectedMetadata }); + this.docLinks.setup(); const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); const uiSettings = this.uiSettings.setup({ http, injectedMetadata }); const notifications = this.notifications.setup({ uiSettings }); @@ -185,7 +185,6 @@ export class CoreSystem { const core: InternalCoreSetup = { application, context, - docLinks, fatalErrors: this.fatalErrorsSetup, http, injectedMetadata, @@ -217,7 +216,7 @@ export class CoreSystem { try { const injectedMetadata = await this.injectedMetadata.start(); const uiSettings = await this.uiSettings.start(); - const docLinks = this.docLinks.start(); + const docLinks = this.docLinks.start({ injectedMetadata }); const http = await this.http.start(); const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); diff --git a/src/core/public/doc_links/doc_links_service.mock.ts b/src/core/public/doc_links/doc_links_service.mock.ts index 9edcf2e3c7990..105c13f96cef6 100644 --- a/src/core/public/doc_links/doc_links_service.mock.ts +++ b/src/core/public/doc_links/doc_links_service.mock.ts @@ -18,25 +18,23 @@ */ import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; -import { DocLinksService, DocLinksSetup, DocLinksStart } from './doc_links_service'; +import { DocLinksService, DocLinksStart } from './doc_links_service'; -const createSetupContractMock = (): DocLinksSetup => { +const createStartContractMock = (): DocLinksStart => { // This service is so simple that we actually use the real implementation const injectedMetadata = injectedMetadataServiceMock.createStartContract(); injectedMetadata.getKibanaBranch.mockReturnValue('mocked-test-branch'); - return new DocLinksService().setup({ injectedMetadata }); + return new DocLinksService().start({ injectedMetadata }); }; -const createStartContractMock: () => DocLinksStart = createSetupContractMock; - type DocLinksServiceContract = PublicMethodsOf; const createMock = (): jest.Mocked => ({ - setup: jest.fn().mockReturnValue(createSetupContractMock()), + setup: jest.fn().mockReturnValue(undefined), start: jest.fn().mockReturnValue(createStartContractMock()), }); export const docLinksServiceMock = { create: createMock, - createSetupContract: createSetupContractMock, + createSetupContract: () => jest.fn(), createStartContract: createStartContractMock, }; diff --git a/src/core/public/doc_links/doc_links_service.test.ts b/src/core/public/doc_links/doc_links_service.test.ts index 4c5d6bcde8b77..c430ae7655040 100644 --- a/src/core/public/doc_links/doc_links_service.test.ts +++ b/src/core/public/doc_links/doc_links_service.test.ts @@ -20,33 +20,15 @@ import { DocLinksService } from './doc_links_service'; import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; -describe('DocLinksService#setup()', () => { +describe('DocLinksService#start()', () => { it('templates the doc links with the branch information from injectedMetadata', () => { const injectedMetadata = injectedMetadataServiceMock.createStartContract(); injectedMetadata.getKibanaBranch.mockReturnValue('test-branch'); const service = new DocLinksService(); - const setup = service.setup({ injectedMetadata }); - expect(setup.DOC_LINK_VERSION).toEqual('test-branch'); - expect(setup.links.kibana).toEqual( + const api = service.start({ injectedMetadata }); + expect(api.DOC_LINK_VERSION).toEqual('test-branch'); + expect(api.links.kibana).toEqual( 'https://www.elastic.co/guide/en/kibana/test-branch/index.html' ); }); }); - -describe('DocLinksService#start()', () => { - it('returns the same data as setup', () => { - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - injectedMetadata.getKibanaBranch.mockReturnValue('test-branch'); - const service = new DocLinksService(); - const setup = service.setup({ injectedMetadata }); - const start = service.start(); - expect(setup).toEqual(start); - }); - - it('must be called after setup', () => { - const service = new DocLinksService(); - expect(() => { - service.start(); - }).toThrowErrorMatchingInlineSnapshot(`"DocLinksService#setup() must be called first!"`); - }); -}); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index f2bc90a5b08d4..0662586797164 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -20,20 +20,19 @@ import { InjectedMetadataSetup } from '../injected_metadata'; import { deepFreeze } from '../../utils'; -interface SetupDeps { +interface StartDeps { injectedMetadata: InjectedMetadataSetup; } /** @internal */ export class DocLinksService { - private service?: DocLinksSetup; - - public setup({ injectedMetadata }: SetupDeps): DocLinksSetup { + public setup() {} + public start({ injectedMetadata }: StartDeps): DocLinksStart { const DOC_LINK_VERSION = injectedMetadata.getKibanaBranch(); const ELASTIC_WEBSITE_URL = 'https://www.elastic.co/'; const ELASTICSEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`; - this.service = deepFreeze({ + return deepFreeze({ DOC_LINK_VERSION, ELASTIC_WEBSITE_URL, links: { @@ -129,21 +128,11 @@ export class DocLinksService { }, }, }); - - return this.service; - } - - public start(): DocLinksStart { - if (!this.service) { - throw new Error(`DocLinksService#setup() must be called first!`); - } - - return this.service; } } /** @public */ -export interface DocLinksSetup { +export interface DocLinksStart { readonly DOC_LINK_VERSION: string; readonly ELASTIC_WEBSITE_URL: string; readonly links: { @@ -236,6 +225,3 @@ export interface DocLinksSetup { readonly management: Record; }; } - -/** @public */ -export type DocLinksStart = DocLinksSetup; diff --git a/src/core/public/doc_links/index.ts b/src/core/public/doc_links/index.ts index fbfa9db5635dd..fe49d4a7c6a58 100644 --- a/src/core/public/doc_links/index.ts +++ b/src/core/public/doc_links/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { DocLinksService, DocLinksSetup, DocLinksStart } from './doc_links_service'; +export { DocLinksService, DocLinksStart } from './doc_links_service'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 99b75f85340f3..41af0f1b8395f 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -67,7 +67,7 @@ import { OverlayStart } from './overlays'; import { Plugin, PluginInitializer, PluginInitializerContext, PluginOpaqueId } from './plugins'; import { UiSettingsState, IUiSettingsClient } from './ui_settings'; import { ApplicationSetup, Capabilities, ApplicationStart } from './application'; -import { DocLinksSetup, DocLinksStart } from './doc_links'; +import { DocLinksStart } from './doc_links'; import { SavedObjectsStart } from './saved_objects'; export { PackageInfo, EnvironmentMode } from '../server/types'; import { @@ -216,8 +216,6 @@ export interface CoreSetup { mockSetupDeps = { application: applicationServiceMock.createInternalSetupContract(), context: contextServiceMock.createSetupContract(), - docLinks: docLinksServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), http: httpServiceMock.createSetupContract(), injectedMetadata: pick(injectedMetadataServiceMock.createStartContract(), 'getInjectedVar'), diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 7970d9f3f86bb..9a79576b14d1f 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -508,8 +508,6 @@ export interface CoreSetup; @@ -600,7 +598,7 @@ export const DEFAULT_APP_CATEGORIES: Readonly<{ }>; // @public (undocumented) -export interface DocLinksSetup { +export interface DocLinksStart { // (undocumented) readonly DOC_LINK_VERSION: string; // (undocumented) @@ -697,9 +695,6 @@ export interface DocLinksSetup { }; } -// @public (undocumented) -export type DocLinksStart = DocLinksSetup; - // @public (undocumented) export interface EnvironmentMode { // (undocumented) @@ -1594,6 +1589,6 @@ export interface UserProvidedValues { // Warnings were encountered during analysis: // -// src/core/public/core_system.ts:216:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts +// src/core/public/core_system.ts:215:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts ``` diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx index e8116409def4b..c78d24f126e29 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx @@ -5,7 +5,7 @@ */ import React, { createContext, useContext } from 'react'; -import { HttpSetup, DocLinksSetup, NotificationsSetup } from 'src/core/public'; +import { HttpSetup, DocLinksStart, NotificationsSetup } from 'src/core/public'; import { getApi, getUseRequest, getSendRequest, getDocumentation } from './lib'; @@ -15,7 +15,7 @@ interface Props { httpClient: HttpSetup; apiBasePath: string; trackMetric: (type: 'loaded' | 'click' | 'count', eventName: string) => void; - docLinks: DocLinksSetup; + docLinks: DocLinksStart; toasts: NotificationsSetup['toasts']; } diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts index dc27dadf0b807..9d20ae9d2ec76 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DocLinksSetup } from 'src/core/public'; +import { DocLinksStart } from 'src/core/public'; -export const getDocumentation = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksSetup) => { +export const getDocumentation = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksStart) => { const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; const esDocsBase = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx index 0b43883728a6f..1ca7f7bff83ed 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx @@ -27,6 +27,7 @@ describe('ExpressionChart', () => { groupBy?: string ) { const mocks = coreMock.createSetup(); + const startMocks = coreMock.createStart(); const [ { application: { capabilities }, @@ -38,7 +39,7 @@ describe('ExpressionChart', () => { toastNotifications: mocks.notifications.toasts, actionTypeRegistry: actionTypeRegistryMock.create() as any, alertTypeRegistry: alertTypeRegistryMock.create() as any, - docLinks: mocks.docLinks, + docLinks: startMocks.docLinks, capabilities: { ...capabilities, actions: { From 78ebb6250a47047d88e3f0c94c9545f633f7c454 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 24 Jun 2020 14:24:53 +0300 Subject: [PATCH 35/36] remove top-nav. it is not rendered in KP (#69488) Co-authored-by: Elastic Machine --- test/functional/page_objects/common_page.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index d08e88ecf47ea..236b2fb9f2f1e 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -378,14 +378,12 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo async isChromeVisible() { const globalNavShown = await globalNav.exists(); - const topNavShown = await testSubjects.exists('top-nav'); - return globalNavShown && topNavShown; + return globalNavShown; } async isChromeHidden() { const globalNavShown = await globalNav.exists(); - const topNavShown = await testSubjects.exists('top-nav'); - return !globalNavShown && !topNavShown; + return !globalNavShown; } async waitForTopNavToBeVisible() { From 2e078dbab96402b96c03908cb3d5a66240b96166 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Wed, 24 Jun 2020 06:54:01 -0500 Subject: [PATCH 36/36] [APM] Storybook theme fixes (#69730) * [APM] Storybook theme fixes The changes adding theme support in #69362 broke some of the Storybook stories. Add decorators to wrap some of the stories in the theme context. This should be done in a global decorator, but our current storybook setup doesn't support this. It also would be nice to be able to switch between light/dark mode, but that's something we can add in the future. * Remove unused import * Adds missing decorator to cytoscape examples + adds a new real-world example Co-authored-by: Oliver Gupte Co-authored-by: Elastic Machine --- .../__stories__/Cytoscape.stories.tsx | 107 +- .../CytoscapeExampleData.stories.tsx | 434 ++-- .../example_response_one_domain_many_ips.json | 2122 +++++++++++++++++ .../index.stories.tsx | 38 +- 4 files changed, 2434 insertions(+), 267 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/example_response_one_domain_many_ips.json diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx index 28cb7a6f9d291..aee392b53298a 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx @@ -10,60 +10,63 @@ import cytoscape from 'cytoscape'; import React from 'react'; import { Cytoscape } from '../Cytoscape'; import { iconForNode } from '../icons'; +import { EuiThemeProvider } from '../../../../../../observability/public'; -storiesOf('app/ServiceMap/Cytoscape', module).add( - 'example', - () => { - const elements: cytoscape.ElementDefinition[] = [ - { - data: { - id: 'opbeans-python', - 'service.name': 'opbeans-python', - 'agent.name': 'python', - }, - }, - { - data: { - id: 'opbeans-node', - 'service.name': 'opbeans-node', - 'agent.name': 'nodejs', - }, - }, - { - data: { - id: 'opbeans-ruby', - 'service.name': 'opbeans-ruby', - 'agent.name': 'ruby', - }, - }, - { data: { source: 'opbeans-python', target: 'opbeans-node' } }, - { - data: { - bidirectional: true, - source: 'opbeans-python', - target: 'opbeans-ruby', - }, - }, - ]; - const height = 300; - const width = 1340; - const serviceName = 'opbeans-python'; - return ( - - ); - }, - { - info: { - propTables: false, - source: false, +storiesOf('app/ServiceMap/Cytoscape', module) + .addDecorator((storyFn) => {storyFn()}) + .add( + 'example', + () => { + const elements: cytoscape.ElementDefinition[] = [ + { + data: { + id: 'opbeans-python', + 'service.name': 'opbeans-python', + 'agent.name': 'python', + }, + }, + { + data: { + id: 'opbeans-node', + 'service.name': 'opbeans-node', + 'agent.name': 'nodejs', + }, + }, + { + data: { + id: 'opbeans-ruby', + 'service.name': 'opbeans-ruby', + 'agent.name': 'ruby', + }, + }, + { data: { source: 'opbeans-python', target: 'opbeans-node' } }, + { + data: { + bidirectional: true, + source: 'opbeans-python', + target: 'opbeans-ruby', + }, + }, + ]; + const height = 300; + const width = 1340; + const serviceName = 'opbeans-python'; + return ( + + ); }, - } -); + { + info: { + propTables: false, + source: false, + }, + } + ); storiesOf('app/ServiceMap/Cytoscape', module).add( 'node icons', diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx index 3aced1b33dcac..44278b2846128 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx @@ -6,23 +6,25 @@ /* eslint-disable no-console */ import { + EuiButton, + EuiCodeEditor, + EuiFieldNumber, + EuiFilePicker, EuiFlexGroup, EuiFlexItem, - EuiButton, EuiForm, - EuiFieldNumber, - EuiToolTip, - EuiCodeEditor, EuiSpacer, - EuiFilePicker, + EuiToolTip, } from '@elastic/eui'; import { storiesOf } from '@storybook/react'; -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; +import { EuiThemeProvider } from '../../../../../../observability/public'; import { Cytoscape } from '../Cytoscape'; -import { generateServiceMapElements } from './generate_service_map_elements'; -import exampleResponseOpbeansBeats from './example_response_opbeans_beats.json'; import exampleResponseHipsterStore from './example_response_hipster_store.json'; +import exampleResponseOpbeansBeats from './example_response_opbeans_beats.json'; import exampleResponseTodo from './example_response_todo.json'; +import exampleResponseOneDomainManyIPs from './example_response_one_domain_many_ips.json'; +import { generateServiceMapElements } from './generate_service_map_elements'; const STORYBOOK_PATH = 'app/ServiceMap/Cytoscape/Example data'; @@ -34,151 +36,155 @@ function setSessionJson(json: string) { window.sessionStorage.setItem(SESSION_STORAGE_KEY, json); } -storiesOf(STORYBOOK_PATH, module).add( - 'Generate map', - () => { - const [size, setSize] = useState(10); - const [json, setJson] = useState(''); - const [elements, setElements] = useState( - generateServiceMapElements(size) - ); - - return ( -
- - - { - setElements(generateServiceMapElements(size)); - setJson(''); - }} - > - Generate service map - - - - - setSize(e.target.valueAsNumber)} - /> - - - - { - setJson(JSON.stringify({ elements }, null, 2)); - }} - > - Get JSON - - - - - - - {json && ( - - )} -
- ); - }, - { - info: { propTables: false, source: false }, - } -); - -storiesOf(STORYBOOK_PATH, module).add( - 'Map from JSON', - () => { - const [json, setJson] = useState( - getSessionJson() || JSON.stringify(exampleResponseTodo, null, 2) - ); - const [error, setError] = useState(); - - const [elements, setElements] = useState([]); - useEffect(() => { - try { - setElements(JSON.parse(json).elements); - } catch (e) { - setError(e.message); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( -
- - +storiesOf(STORYBOOK_PATH, module) + .addDecorator((storyFn) => {storyFn()}) + .add( + 'Generate map', + () => { + const [size, setSize] = useState(10); + const [json, setJson] = useState(''); + const [elements, setElements] = useState( + generateServiceMapElements(size) + ); + + return ( +
- { - setJson(value); + { + setElements(generateServiceMapElements(size)); + setJson(''); }} - /> + > + Generate service map + - - { - const item = event?.item(0); - - if (item) { - const f = new FileReader(); - f.onload = (onloadEvent) => { - const result = onloadEvent?.target?.result; - if (typeof result === 'string') { - setJson(result); - } - }; - f.readAsText(item); - } - }} + + setSize(e.target.valueAsNumber)} /> - - { - try { - setElements(JSON.parse(json).elements); - setSessionJson(json); - setError(undefined); - } catch (e) { - setError(e.message); - } - }} - > - Render JSON - - + + + + { + setJson(JSON.stringify({ elements }, null, 2)); + }} + > + Get JSON + - -
- ); - }, - { - info: { - propTables: false, - source: false, - text: ` + + + + {json && ( + + )} +
+ ); + }, + { + info: { propTables: false, source: false }, + } + ); + +storiesOf(STORYBOOK_PATH, module) + .addDecorator((storyFn) => {storyFn()}) + .add( + 'Map from JSON', + () => { + const [json, setJson] = useState( + getSessionJson() || JSON.stringify(exampleResponseTodo, null, 2) + ); + const [error, setError] = useState(); + + const [elements, setElements] = useState([]); + useEffect(() => { + try { + setElements(JSON.parse(json).elements); + } catch (e) { + setError(e.message); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
+ + + + + { + setJson(value); + }} + /> + + + + { + const item = event?.item(0); + + if (item) { + const f = new FileReader(); + f.onload = (onloadEvent) => { + const result = onloadEvent?.target?.result; + if (typeof result === 'string') { + setJson(result); + } + }; + f.readAsText(item); + } + }} + /> + + { + try { + setElements(JSON.parse(json).elements); + setSessionJson(json); + setError(undefined); + } catch (e) { + setError(e.message); + } + }} + > + Render JSON + + + + + +
+ ); + }, + { + info: { + propTables: false, + source: false, + text: ` Enter JSON map data into the text box or upload a file and click "Render JSON" to see the results. You can enable a download button on the service map by putting \`\`\` @@ -186,60 +192,86 @@ storiesOf(STORYBOOK_PATH, module).add( \`\`\` into the JavaScript console and reloading the page.`, + }, + } + ); + +storiesOf(STORYBOOK_PATH, module) + .addDecorator((storyFn) => {storyFn()}) + .add( + 'Todo app', + () => { + return ( +
+ +
+ ); + }, + { + info: { propTables: false, source: false }, + } + ); + +storiesOf(STORYBOOK_PATH, module) + .addDecorator((storyFn) => {storyFn()}) + .add( + 'Opbeans + beats', + () => { + return ( +
+ +
+ ); + }, + { + info: { propTables: false, source: false }, + } + ); + +storiesOf(STORYBOOK_PATH, module) + .addDecorator((storyFn) => {storyFn()}) + .add( + 'Hipster store', + () => { + return ( +
+ +
+ ); + }, + { + info: { propTables: false, source: false }, + } + ); + +storiesOf(STORYBOOK_PATH, module) + .addDecorator((storyFn) => {storyFn()}) + .add( + 'Node resolves one domain name to many IPs', + () => { + return ( +
+ +
+ ); }, - } -); - -storiesOf(STORYBOOK_PATH, module).add( - 'Todo app', - () => { - return ( -
- -
- ); - }, - { - info: { propTables: false, source: false }, - } -); - -storiesOf(STORYBOOK_PATH, module).add( - 'Opbeans + beats', - () => { - return ( -
- -
- ); - }, - { - info: { propTables: false, source: false }, - } -); - -storiesOf(STORYBOOK_PATH, module).add( - 'Hipster store', - () => { - return ( -
- -
- ); - }, - { - info: { propTables: false, source: false }, - } -); + { + info: { propTables: false, source: false }, + } + ); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/example_response_one_domain_many_ips.json b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/example_response_one_domain_many_ips.json new file mode 100644 index 0000000000000..f9b8a273d8577 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/example_response_one_domain_many_ips.json @@ -0,0 +1,2122 @@ +{ + "elements": [ + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.99:80", + "id": "artifact_api~>192.0.2.99:80", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "http", + "span.destination.service.resource": "192.0.2.99:80", + "span.type": "external", + "id": ">192.0.2.99:80", + "label": ">192.0.2.99:80" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.47:443", + "id": "artifact_api~>192.0.2.47:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.47:443", + "span.type": "external", + "id": ">192.0.2.47:443", + "label": ">192.0.2.47:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.13:443", + "id": "artifact_api~>192.0.2.13:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.13:443", + "span.type": "external", + "id": ">192.0.2.13:443", + "label": ">192.0.2.13:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.106:443", + "id": "artifact_api~>192.0.2.106:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.106:443", + "span.type": "external", + "id": ">192.0.2.106:443", + "label": ">192.0.2.106:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.83:443", + "id": "artifact_api~>192.0.2.83:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.83:443", + "span.type": "external", + "id": ">192.0.2.83:443", + "label": ">192.0.2.83:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.111:443", + "id": "artifact_api~>192.0.2.111:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.111:443", + "span.type": "external", + "id": ">192.0.2.111:443", + "label": ">192.0.2.111:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.189:443", + "id": "artifact_api~>192.0.2.189:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.189:443", + "span.type": "external", + "id": ">192.0.2.189:443", + "label": ">192.0.2.189:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.148:443", + "id": "artifact_api~>192.0.2.148:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.148:443", + "span.type": "external", + "id": ">192.0.2.148:443", + "label": ">192.0.2.148:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.39:443", + "id": "artifact_api~>192.0.2.39:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.39:443", + "span.type": "external", + "id": ">192.0.2.39:443", + "label": ">192.0.2.39:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.42:443", + "id": "artifact_api~>192.0.2.42:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.42:443", + "span.type": "external", + "id": ">192.0.2.42:443", + "label": ">192.0.2.42:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.240:443", + "id": "artifact_api~>192.0.2.240:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.240:443", + "span.type": "external", + "id": ">192.0.2.240:443", + "label": ">192.0.2.240:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.156:443", + "id": "artifact_api~>192.0.2.156:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.156:443", + "span.type": "external", + "id": ">192.0.2.156:443", + "label": ">192.0.2.156:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.245:443", + "id": "artifact_api~>192.0.2.245:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.245:443", + "span.type": "external", + "id": ">192.0.2.245:443", + "label": ">192.0.2.245:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.198:443", + "id": "artifact_api~>192.0.2.198:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.198:443", + "span.type": "external", + "id": ">192.0.2.198:443", + "label": ">192.0.2.198:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.77:443", + "id": "artifact_api~>192.0.2.77:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.77:443", + "span.type": "external", + "id": ">192.0.2.77:443", + "label": ">192.0.2.77:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.8:443", + "id": "artifact_api~>192.0.2.8:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.8:443", + "span.type": "external", + "id": ">192.0.2.8:443", + "label": ">192.0.2.8:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.69:443", + "id": "artifact_api~>192.0.2.69:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.69:443", + "span.type": "external", + "id": ">192.0.2.69:443", + "label": ">192.0.2.69:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.5:443", + "id": "artifact_api~>192.0.2.5:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.5:443", + "span.type": "external", + "id": ">192.0.2.5:443", + "label": ">192.0.2.5:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.139:443", + "id": "artifact_api~>192.0.2.139:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.139:443", + "span.type": "external", + "id": ">192.0.2.139:443", + "label": ">192.0.2.139:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.113:443", + "id": "artifact_api~>192.0.2.113:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.113:443", + "span.type": "external", + "id": ">192.0.2.113:443", + "label": ">192.0.2.113:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.2:443", + "id": "artifact_api~>192.0.2.2:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.2:443", + "span.type": "external", + "id": ">192.0.2.2:443", + "label": ">192.0.2.2:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.213:443", + "id": "artifact_api~>192.0.2.213:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.213:443", + "span.type": "external", + "id": ">192.0.2.213:443", + "label": ">192.0.2.213:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.153:443", + "id": "artifact_api~>192.0.2.153:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.153:443", + "span.type": "external", + "id": ">192.0.2.153:443", + "label": ">192.0.2.153:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.36:443", + "id": "artifact_api~>192.0.2.36:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.36:443", + "span.type": "external", + "id": ">192.0.2.36:443", + "label": ">192.0.2.36:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.164:443", + "id": "artifact_api~>192.0.2.164:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.164:443", + "span.type": "external", + "id": ">192.0.2.164:443", + "label": ">192.0.2.164:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.190:443", + "id": "artifact_api~>192.0.2.190:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.190:443", + "span.type": "external", + "id": ">192.0.2.190:443", + "label": ">192.0.2.190:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.9:443", + "id": "artifact_api~>192.0.2.9:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.9:443", + "span.type": "external", + "id": ">192.0.2.9:443", + "label": ">192.0.2.9:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.210:443", + "id": "artifact_api~>192.0.2.210:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.210:443", + "span.type": "external", + "id": ">192.0.2.210:443", + "label": ">192.0.2.210:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.21:443", + "id": "artifact_api~>192.0.2.21:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.21:443", + "span.type": "external", + "id": ">192.0.2.21:443", + "label": ">192.0.2.21:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.176:443", + "id": "artifact_api~>192.0.2.176:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.176:443", + "span.type": "external", + "id": ">192.0.2.176:443", + "label": ">192.0.2.176:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.81:443", + "id": "artifact_api~>192.0.2.81:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.81:443", + "span.type": "external", + "id": ">192.0.2.81:443", + "label": ">192.0.2.81:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.118:443", + "id": "artifact_api~>192.0.2.118:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.118:443", + "span.type": "external", + "id": ">192.0.2.118:443", + "label": ">192.0.2.118:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.103:443", + "id": "artifact_api~>192.0.2.103:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.103:443", + "span.type": "external", + "id": ">192.0.2.103:443", + "label": ">192.0.2.103:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.3:443", + "id": "artifact_api~>192.0.2.3:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.3:443", + "span.type": "external", + "id": ">192.0.2.3:443", + "label": ">192.0.2.3:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.135:443", + "id": "artifact_api~>192.0.2.135:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.135:443", + "span.type": "external", + "id": ">192.0.2.135:443", + "label": ">192.0.2.135:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.26:443", + "id": "artifact_api~>192.0.2.26:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.26:443", + "span.type": "external", + "id": ">192.0.2.26:443", + "label": ">192.0.2.26:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.185:443", + "id": "artifact_api~>192.0.2.185:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.185:443", + "span.type": "external", + "id": ">192.0.2.185:443", + "label": ">192.0.2.185:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.173:443", + "id": "artifact_api~>192.0.2.173:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.173:443", + "span.type": "external", + "id": ">192.0.2.173:443", + "label": ">192.0.2.173:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.45:443", + "id": "artifact_api~>192.0.2.45:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.45:443", + "span.type": "external", + "id": ">192.0.2.45:443", + "label": ">192.0.2.45:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.144:443", + "id": "artifact_api~>192.0.2.144:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.144:443", + "span.type": "external", + "id": ">192.0.2.144:443", + "label": ">192.0.2.144:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.165:443", + "id": "artifact_api~>192.0.2.165:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.165:443", + "span.type": "external", + "id": ">192.0.2.165:443", + "label": ">192.0.2.165:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.119:443", + "id": "artifact_api~>192.0.2.119:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.119:443", + "span.type": "external", + "id": ">192.0.2.119:443", + "label": ">192.0.2.119:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.186:443", + "id": "artifact_api~>192.0.2.186:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.186:443", + "span.type": "external", + "id": ">192.0.2.186:443", + "label": ">192.0.2.186:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.54:443", + "id": "artifact_api~>192.0.2.54:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.54:443", + "span.type": "external", + "id": ">192.0.2.54:443", + "label": ">192.0.2.54:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.23:443", + "id": "artifact_api~>192.0.2.23:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.23:443", + "span.type": "external", + "id": ">192.0.2.23:443", + "label": ">192.0.2.23:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.34:443", + "id": "artifact_api~>192.0.2.34:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.34:443", + "span.type": "external", + "id": ">192.0.2.34:443", + "label": ">192.0.2.34:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.169:443", + "id": "artifact_api~>192.0.2.169:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.169:443", + "span.type": "external", + "id": ">192.0.2.169:443", + "label": ">192.0.2.169:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.226:443", + "id": "artifact_api~>192.0.2.226:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.226:443", + "span.type": "external", + "id": ">192.0.2.226:443", + "label": ">192.0.2.226:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.82:443", + "id": "artifact_api~>192.0.2.82:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.82:443", + "span.type": "external", + "id": ">192.0.2.82:443", + "label": ">192.0.2.82:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.132:443", + "id": "artifact_api~>192.0.2.132:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.132:443", + "span.type": "external", + "id": ">192.0.2.132:443", + "label": ">192.0.2.132:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.78:443", + "id": "artifact_api~>192.0.2.78:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.78:443", + "span.type": "external", + "id": ">192.0.2.78:443", + "label": ">192.0.2.78:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.71:443", + "id": "artifact_api~>192.0.2.71:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.71:443", + "span.type": "external", + "id": ">192.0.2.71:443", + "label": ">192.0.2.71:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.48:443", + "id": "artifact_api~>192.0.2.48:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.48:443", + "span.type": "external", + "id": ">192.0.2.48:443", + "label": ">192.0.2.48:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.107:443", + "id": "artifact_api~>192.0.2.107:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.107:443", + "span.type": "external", + "id": ">192.0.2.107:443", + "label": ">192.0.2.107:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.239:443", + "id": "artifact_api~>192.0.2.239:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.239:443", + "span.type": "external", + "id": ">192.0.2.239:443", + "label": ">192.0.2.239:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.209:443", + "id": "artifact_api~>192.0.2.209:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.209:443", + "span.type": "external", + "id": ">192.0.2.209:443", + "label": ">192.0.2.209:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.248:443", + "id": "artifact_api~>192.0.2.248:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.248:443", + "span.type": "external", + "id": ">192.0.2.248:443", + "label": ">192.0.2.248:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.18:443", + "id": "artifact_api~>192.0.2.18:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.18:443", + "span.type": "external", + "id": ">192.0.2.18:443", + "label": ">192.0.2.18:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.228:443", + "id": "artifact_api~>192.0.2.228:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.228:443", + "span.type": "external", + "id": ">192.0.2.228:443", + "label": ">192.0.2.228:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.145:443", + "id": "artifact_api~>192.0.2.145:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.145:443", + "span.type": "external", + "id": ">192.0.2.145:443", + "label": ">192.0.2.145:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.25:443", + "id": "artifact_api~>192.0.2.25:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.25:443", + "span.type": "external", + "id": ">192.0.2.25:443", + "label": ">192.0.2.25:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.162:443", + "id": "artifact_api~>192.0.2.162:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.162:443", + "span.type": "external", + "id": ">192.0.2.162:443", + "label": ">192.0.2.162:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.202:443", + "id": "artifact_api~>192.0.2.202:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.202:443", + "span.type": "external", + "id": ">192.0.2.202:443", + "label": ">192.0.2.202:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.60:443", + "id": "artifact_api~>192.0.2.60:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.60:443", + "span.type": "external", + "id": ">192.0.2.60:443", + "label": ">192.0.2.60:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.59:443", + "id": "artifact_api~>192.0.2.59:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.59:443", + "span.type": "external", + "id": ">192.0.2.59:443", + "label": ">192.0.2.59:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.114:443", + "id": "artifact_api~>192.0.2.114:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.114:443", + "span.type": "external", + "id": ">192.0.2.114:443", + "label": ">192.0.2.114:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.215:443", + "id": "artifact_api~>192.0.2.215:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.215:443", + "span.type": "external", + "id": ">192.0.2.215:443", + "label": ">192.0.2.215:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.238:443", + "id": "artifact_api~>192.0.2.238:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.238:443", + "span.type": "external", + "id": ">192.0.2.238:443", + "label": ">192.0.2.238:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.160:443", + "id": "artifact_api~>192.0.2.160:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.160:443", + "span.type": "external", + "id": ">192.0.2.160:443", + "label": ">192.0.2.160:443" + } + } + }, + { + "data": { + "source": "artifact_api", + "target": ">192.0.2.70:443", + "id": "artifact_api~>192.0.2.70:443", + "sourceData": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + }, + "targetData": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.70:443", + "span.type": "external", + "id": ">192.0.2.70:443", + "label": ">192.0.2.70:443" + } + } + }, + { + "data": { + "id": "artifact_api", + "service.environment": "development", + "service.name": "artifact_api", + "agent.name": "nodejs", + "service.framework.name": "express" + } + }, + { + "data": { + "span.subtype": "http", + "span.destination.service.resource": "192.0.2.99:80", + "span.type": "external", + "id": ">192.0.2.99:80", + "label": ">192.0.2.99:80" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.186:443", + "span.type": "external", + "id": ">192.0.2.186:443", + "label": ">192.0.2.186:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.78:443", + "span.type": "external", + "id": ">192.0.2.78:443", + "label": ">192.0.2.78:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.226:443", + "span.type": "external", + "id": ">192.0.2.226:443", + "label": ">192.0.2.226:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.245:443", + "span.type": "external", + "id": ">192.0.2.245:443", + "label": ">192.0.2.245:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.77:443", + "span.type": "external", + "id": ">192.0.2.77:443", + "label": ">192.0.2.77:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.2:443", + "span.type": "external", + "id": ">192.0.2.2:443", + "label": ">192.0.2.2:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.198:443", + "span.type": "external", + "id": ">192.0.2.198:443", + "label": ">192.0.2.198:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.113:443", + "span.type": "external", + "id": ">192.0.2.113:443", + "label": ">192.0.2.113:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.39:443", + "span.type": "external", + "id": ">192.0.2.39:443", + "label": ">192.0.2.39:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.83:443", + "span.type": "external", + "id": ">192.0.2.83:443", + "label": ">192.0.2.83:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.5:443", + "span.type": "external", + "id": ">192.0.2.5:443", + "label": ">192.0.2.5:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.165:443", + "span.type": "external", + "id": ">192.0.2.165:443", + "label": ">192.0.2.165:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.156:443", + "span.type": "external", + "id": ">192.0.2.156:443", + "label": ">192.0.2.156:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.132:443", + "span.type": "external", + "id": ">192.0.2.132:443", + "label": ">192.0.2.132:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.240:443", + "span.type": "external", + "id": ">192.0.2.240:443", + "label": ">192.0.2.240:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.54:443", + "span.type": "external", + "id": ">192.0.2.54:443", + "label": ">192.0.2.54:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.213:443", + "span.type": "external", + "id": ">192.0.2.213:443", + "label": ">192.0.2.213:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.81:443", + "span.type": "external", + "id": ">192.0.2.81:443", + "label": ">192.0.2.81:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.176:443", + "span.type": "external", + "id": ">192.0.2.176:443", + "label": ">192.0.2.176:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.82:443", + "span.type": "external", + "id": ">192.0.2.82:443", + "label": ">192.0.2.82:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.23:443", + "span.type": "external", + "id": ">192.0.2.23:443", + "label": ">192.0.2.23:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.189:443", + "span.type": "external", + "id": ">192.0.2.189:443", + "label": ">192.0.2.189:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.190:443", + "span.type": "external", + "id": ">192.0.2.190:443", + "label": ">192.0.2.190:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.119:443", + "span.type": "external", + "id": ">192.0.2.119:443", + "label": ">192.0.2.119:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.169:443", + "span.type": "external", + "id": ">192.0.2.169:443", + "label": ">192.0.2.169:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.210:443", + "span.type": "external", + "id": ">192.0.2.210:443", + "label": ">192.0.2.210:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.148:443", + "span.type": "external", + "id": ">192.0.2.148:443", + "label": ">192.0.2.148:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.26:443", + "span.type": "external", + "id": ">192.0.2.26:443", + "label": ">192.0.2.26:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.139:443", + "span.type": "external", + "id": ">192.0.2.139:443", + "label": ">192.0.2.139:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.111:443", + "span.type": "external", + "id": ">192.0.2.111:443", + "label": ">192.0.2.111:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.13:443", + "span.type": "external", + "id": ">192.0.2.13:443", + "label": ">192.0.2.13:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.36:443", + "span.type": "external", + "id": ">192.0.2.36:443", + "label": ">192.0.2.36:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.69:443", + "span.type": "external", + "id": ">192.0.2.69:443", + "label": ">192.0.2.69:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.173:443", + "span.type": "external", + "id": ">192.0.2.173:443", + "label": ">192.0.2.173:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.144:443", + "span.type": "external", + "id": ">192.0.2.144:443", + "label": ">192.0.2.144:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.135:443", + "span.type": "external", + "id": ">192.0.2.135:443", + "label": ">192.0.2.135:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.21:443", + "span.type": "external", + "id": ">192.0.2.21:443", + "label": ">192.0.2.21:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.118:443", + "span.type": "external", + "id": ">192.0.2.118:443", + "label": ">192.0.2.118:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.42:443", + "span.type": "external", + "id": ">192.0.2.42:443", + "label": ">192.0.2.42:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.106:443", + "span.type": "external", + "id": ">192.0.2.106:443", + "label": ">192.0.2.106:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.3:443", + "span.type": "external", + "id": ">192.0.2.3:443", + "label": ">192.0.2.3:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.34:443", + "span.type": "external", + "id": ">192.0.2.34:443", + "label": ">192.0.2.34:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.185:443", + "span.type": "external", + "id": ">192.0.2.185:443", + "label": ">192.0.2.185:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.153:443", + "span.type": "external", + "id": ">192.0.2.153:443", + "label": ">192.0.2.153:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.9:443", + "span.type": "external", + "id": ">192.0.2.9:443", + "label": ">192.0.2.9:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.164:443", + "span.type": "external", + "id": ">192.0.2.164:443", + "label": ">192.0.2.164:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.47:443", + "span.type": "external", + "id": ">192.0.2.47:443", + "label": ">192.0.2.47:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.45:443", + "span.type": "external", + "id": ">192.0.2.45:443", + "label": ">192.0.2.45:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.8:443", + "span.type": "external", + "id": ">192.0.2.8:443", + "label": ">192.0.2.8:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.103:443", + "span.type": "external", + "id": ">192.0.2.103:443", + "label": ">192.0.2.103:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.60:443", + "span.type": "external", + "id": ">192.0.2.60:443", + "label": ">192.0.2.60:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.202:443", + "span.type": "external", + "id": ">192.0.2.202:443", + "label": ">192.0.2.202:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.70:443", + "span.type": "external", + "id": ">192.0.2.70:443", + "label": ">192.0.2.70:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.114:443", + "span.type": "external", + "id": ">192.0.2.114:443", + "label": ">192.0.2.114:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.25:443", + "span.type": "external", + "id": ">192.0.2.25:443", + "label": ">192.0.2.25:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.209:443", + "span.type": "external", + "id": ">192.0.2.209:443", + "label": ">192.0.2.209:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.248:443", + "span.type": "external", + "id": ">192.0.2.248:443", + "label": ">192.0.2.248:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.18:443", + "span.type": "external", + "id": ">192.0.2.18:443", + "label": ">192.0.2.18:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.107:443", + "span.type": "external", + "id": ">192.0.2.107:443", + "label": ">192.0.2.107:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.160:443", + "span.type": "external", + "id": ">192.0.2.160:443", + "label": ">192.0.2.160:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.228:443", + "span.type": "external", + "id": ">192.0.2.228:443", + "label": ">192.0.2.228:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.215:443", + "span.type": "external", + "id": ">192.0.2.215:443", + "label": ">192.0.2.215:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.162:443", + "span.type": "external", + "id": ">192.0.2.162:443", + "label": ">192.0.2.162:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.238:443", + "span.type": "external", + "id": ">192.0.2.238:443", + "label": ">192.0.2.238:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.145:443", + "span.type": "external", + "id": ">192.0.2.145:443", + "label": ">192.0.2.145:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.239:443", + "span.type": "external", + "id": ">192.0.2.239:443", + "label": ">192.0.2.239:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.59:443", + "span.type": "external", + "id": ">192.0.2.59:443", + "label": ">192.0.2.59:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.71:443", + "span.type": "external", + "id": ">192.0.2.71:443", + "label": ">192.0.2.71:443" + } + }, + { + "data": { + "span.subtype": "https", + "span.destination.service.resource": "192.0.2.48:443", + "span.type": "external", + "id": ">192.0.2.48:443", + "label": ">192.0.2.48:443" + } + }, + { + "data": { + "service.name": "graphics-worker", + "agent.name": "nodejs", + "service.environment": null, + "service.framework.name": null, + "id": "graphics-worker" + } + } + ] +} diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx index 7c69fb28d668f..920ef39e84ca3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -21,13 +21,13 @@ import { ApmPluginContext, ApmPluginContextValue, } from '../../../../../context/ApmPluginContext'; +import { EuiThemeProvider } from '../../../../../../../observability/public'; storiesOf( 'app/Settings/AgentConfigurations/AgentConfigurationCreateEdit', module -).add( - 'with config', - () => { +) + .addDecorator((storyFn) => { const httpMock = {}; // mock @@ -40,10 +40,21 @@ storiesOf( }, }, }; + return ( - + + + {storyFn()} + + + ); + }) + .add( + 'with config', + () => { + return ( - - ); - }, - { - info: { - source: false, + ); }, - } -); + { + info: { + source: false, + }, + } + );