From 309fe76742f934f4f328927c1492dbc146eb7231 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Tue, 15 Sep 2020 16:24:04 -0400 Subject: [PATCH 01/15] [Ingest pipelines] Polish pipeline debugging workflow (#76058) --- .../pipeline_processors_editor.helpers.tsx | 10 +- .../pipeline_processors_editor.test.tsx | 2 +- .../__jest__/test_pipeline.helpers.tsx | 7 +- .../__jest__/test_pipeline.test.tsx | 17 +- .../documents_dropdown.scss | 3 - .../documents_dropdown/documents_dropdown.tsx | 70 ----- .../components/index.ts | 8 +- .../manage_processor_form.container.tsx | 74 ----- .../manage_processor_form.tsx | 236 ---------------- .../processor_output.tsx | 217 --------------- .../pipeline_processors_editor_item.scss | 2 +- ...pipeline_processors_editor_item_status.tsx | 21 +- .../processor_form/add_processor_form.tsx | 134 ++++++++++ .../documentation_button.tsx | 0 .../processor_form/edit_processor_form.tsx | 253 ++++++++++++++++++ .../field_components/index.ts | 0 .../field_components/text_editor.tsx | 0 .../field_components/xjson_editor.tsx | 0 .../index.ts | 6 +- .../processor_form.container.tsx | 127 +++++++++ .../processor_form/processor_output/index.ts | 7 + .../processor_output/processor_output.scss | 12 + .../processor_output/processor_output.tsx | 240 +++++++++++++++++ .../processor_settings_fields.tsx | 0 .../processors/append.tsx | 0 .../processors/bytes.tsx | 0 .../processors/circle.tsx | 0 .../common_fields/common_processor_fields.tsx | 0 .../common_fields/field_name_field.tsx | 0 .../common_fields/ignore_missing_field.tsx | 0 .../processors/common_fields/index.ts | 0 .../common_fields/processor_type_field.tsx | 0 .../common_fields/properties_field.tsx | 0 .../processors/common_fields/target_field.tsx | 0 .../processors/convert.tsx | 0 .../processors/csv.tsx | 0 .../processors/custom.tsx | 3 +- .../processors/date.tsx | 0 .../processors/date_index_name.tsx | 0 .../processors/dissect.tsx | 0 .../processors/dot_expander.tsx | 0 .../processors/drop.tsx | 0 .../processors/enrich.tsx | 0 .../processors/fail.tsx | 0 .../processors/foreach.tsx | 0 .../processors/geoip.tsx | 0 .../processors/grok.tsx | 0 .../processors/gsub.tsx | 0 .../processors/html_strip.tsx | 0 .../processors/index.ts | 0 .../processors/inference.tsx | 0 .../processors/join.tsx | 0 .../processors/json.tsx | 0 .../processors/kv.tsx | 0 .../processors/lowercase.tsx | 0 .../processors/pipeline.tsx | 0 .../processors/remove.tsx | 0 .../processors/rename.tsx | 0 .../processors/script.tsx | 0 .../processors/set.tsx | 0 .../processors/set_security_user.tsx | 0 .../processors/shared.ts | 0 .../processors/sort.tsx | 0 .../processors/split.tsx | 0 .../processors/trim.tsx | 0 .../processors/uppercase.tsx | 0 .../processors/url_decode.tsx | 0 .../processors/user_agent.tsx | 0 .../components/shared/index.ts | 2 + .../shared/map_processor_type_to_form.tsx | 2 +- .../shared/status_icons/error_icon.tsx | 18 ++ .../status_icons/error_ignored_icon.tsx | 24 ++ .../components/shared/status_icons/index.ts | 9 + .../shared/status_icons/skipped_icon.tsx | 18 ++ .../test_pipeline/add_documents_button.tsx | 8 +- .../documents_dropdown.scss | 3 + .../documents_dropdown/documents_dropdown.tsx | 138 ++++++++++ .../documents_dropdown/index.ts | 0 .../test_pipeline/test_output_button.tsx | 40 +-- .../test_pipeline/test_pipeline_actions.tsx | 56 ++-- .../test_pipeline_flyout.container.tsx | 177 ++++++++++++ .../test_pipeline/test_pipeline_flyout.tsx | 151 ++++------- .../tab_documents.tsx | 137 +++------- .../test_pipeline_flyout_tabs/tab_output.tsx | 27 +- .../test_pipeline_tabs.tsx | 12 +- .../context/processors_context.tsx | 12 +- .../context/test_pipeline_context.tsx | 10 +- .../pipeline_processors_editor/deserialize.ts | 41 ++- .../pipeline_processors_editor/index.ts | 7 +- .../pipeline_processors_editor/types.ts | 2 +- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 92 files changed, 1411 insertions(+), 938 deletions(-) delete mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.scss delete mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx delete mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx delete mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.tsx delete mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/documentation_button.tsx (100%) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/field_components/index.ts (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/field_components/text_editor.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/field_components/xjson_editor.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/index.ts (71%) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/index.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.scss create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.tsx rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processor_settings_fields.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/append.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/bytes.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/circle.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/common_fields/common_processor_fields.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/common_fields/field_name_field.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/common_fields/ignore_missing_field.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/common_fields/index.ts (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/common_fields/processor_type_field.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/common_fields/properties_field.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/common_fields/target_field.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/convert.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/csv.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/custom.tsx (96%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/date.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/date_index_name.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/dissect.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/dot_expander.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/drop.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/enrich.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/fail.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/foreach.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/geoip.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/grok.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/gsub.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/html_strip.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/index.ts (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/inference.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/join.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/json.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/kv.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/lowercase.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/pipeline.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/remove.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/rename.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/script.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/set.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/set_security_user.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/shared.ts (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/sort.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/split.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/trim.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/uppercase.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/url_decode.tsx (100%) rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{manage_processor_form => processor_form}/processors/user_agent.tsx (100%) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_icon.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_ignored_icon.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/index.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/skipped_icon.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.scss create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx rename x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/{ => test_pipeline}/documents_dropdown/index.ts (100%) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.container.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx index 2e7a47e0c93de..e46e5156e30f3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx @@ -126,7 +126,7 @@ const createActions = (testBed: TestBed) => { }); }); await act(async () => { - find('processorSettingsForm.submitButton').simulate('click'); + find('addProcessorForm.submitButton').simulate('click'); }); }, @@ -166,7 +166,7 @@ const createActions = (testBed: TestBed) => { }); }); await act(async () => { - find('processorSettingsForm.submitButton').simulate('click'); + find('addProcessorForm.submitButton').simulate('click'); }); }, @@ -202,8 +202,10 @@ type TestSubject = | 'pipelineEditorDoneButton' | 'pipelineEditorOnFailureToggle' | 'addProcessorsButtonLevel1' - | 'processorSettingsForm' - | 'processorSettingsForm.submitButton' + | 'editProcessorForm' + | 'editProcessorForm.submitButton' + | 'addProcessorForm.submitButton' + | 'addProcessorForm' | 'processorOptionsEditor' | 'processorSettingsFormFlyout' | 'processorTypeSelector' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx index 38c652f41e5e1..74ae8b8894b9f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx @@ -180,7 +180,7 @@ describe('Pipeline Editor', () => { it('prevents moving a processor while in edit mode', () => { const { find, exists } = testBed; find('processors>0.manageItemButton').simulate('click'); - expect(exists('processorSettingsForm')).toBe(true); + expect(exists('editProcessorForm')).toBe(true); expect(find('processors>0.moveItemButton').props().disabled).toBe(true); expect(find('processors>1.moveItemButton').props().disabled).toBe(true); }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx index fec3259fa019b..f4c89d7a1058a 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx @@ -140,8 +140,8 @@ const createActions = (testBed: TestBed) => { component.update(); }, - clickProcessorOutputTab() { - act(() => { + async clickProcessorOutputTab() { + await act(async () => { find('outputTab').simulate('click'); }); component.update(); @@ -224,7 +224,8 @@ type TestSubject = | 'processorStatusIcon' | 'documentsTab' | 'manageItemButton' - | 'processorSettingsForm' + | 'addProcessorForm' + | 'editProcessorForm' | 'configurationTab' | 'outputTab' | 'processorOutputTabContent' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx index 339c840bb86f1..e5118a6e465af 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx @@ -44,7 +44,7 @@ describe('Test pipeline', () => { describe('Test pipeline actions', () => { it('should successfully add sample documents and execute the pipeline', async () => { - const { find, actions, exists } = testBed; + const { actions, exists } = testBed; httpRequestsMockHelpers.setSimulatePipelineResponse(SIMULATE_RESPONSE); @@ -59,7 +59,6 @@ describe('Test pipeline', () => { expect(exists('testPipelineFlyout')).toBe(true); expect(exists('documentsTabContent')).toBe(true); expect(exists('outputTabContent')).toBe(false); - expect(find('outputTab').props().disabled).toEqual(true); // Add sample documents and click run actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); @@ -89,21 +88,25 @@ describe('Test pipeline', () => { }); // Verify output tab is active - expect(find('outputTab').props().disabled).toEqual(false); expect(exists('documentsTabContent')).toBe(false); expect(exists('outputTabContent')).toBe(true); // Click reload button and verify request const totalRequests = server.requests.length; await actions.clickRefreshOutputButton(); - expect(server.requests.length).toBe(totalRequests + 1); + // There will be two requests made to the simulate API + // the second request will have verbose enabled to update the processor results + expect(server.requests.length).toBe(totalRequests + 2); + expect(server.requests[server.requests.length - 2].url).toBe( + '/api/ingest_pipelines/simulate' + ); expect(server.requests[server.requests.length - 1].url).toBe( '/api/ingest_pipelines/simulate' ); // Click verbose toggle and verify request await actions.toggleVerboseSwitch(); - expect(server.requests.length).toBe(totalRequests + 2); + expect(server.requests.length).toBe(totalRequests + 3); expect(server.requests[server.requests.length - 1].url).toBe( '/api/ingest_pipelines/simulate' ); @@ -228,10 +231,10 @@ describe('Test pipeline', () => { // Click processor to open manage flyout await actions.clickProcessor('processors>0'); // Verify flyout opened - expect(exists('processorSettingsForm')).toBe(true); + expect(exists('editProcessorForm')).toBe(true); // Navigate to "Output" tab - actions.clickProcessorOutputTab(); + await actions.clickProcessorOutputTab(); // Verify content expect(exists('processorOutputTabContent')).toBe(true); }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.scss deleted file mode 100644 index c5b14dc129b0e..0000000000000 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.scss +++ /dev/null @@ -1,3 +0,0 @@ -.documentsDropdown__selectContainer { - max-width: 200px; -} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx deleted file mode 100644 index e26b6a2890fe4..0000000000000 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx +++ /dev/null @@ -1,70 +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 { i18n } from '@kbn/i18n'; -import React, { FunctionComponent } from 'react'; -import { EuiSelect, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; - -import { Document } from '../../types'; - -import './documents_dropdown.scss'; - -const i18nTexts = { - ariaLabel: i18n.translate( - 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsDropdownAriaLabel', - { - defaultMessage: 'Select documents', - } - ), - dropdownLabel: i18n.translate( - 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsdropdownLabel', - { - defaultMessage: 'Documents:', - } - ), - buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.buttonLabel', { - defaultMessage: 'Add documents', - }), -}; - -const getDocumentOptions = (documents: Document[]) => - documents.map((doc, index) => ({ - value: index, - text: doc._id, - })); - -interface Props { - documents: Document[]; - selectedDocumentIndex: number; - updateSelectedDocument: (index: number) => void; -} - -export const DocumentsDropdown: FunctionComponent = ({ - documents, - selectedDocumentIndex, - updateSelectedDocument, -}) => { - return ( - - - - {i18nTexts.dropdownLabel} - - - - { - updateSelectedDocument(Number(e.target.value)); - }} - aria-label={i18nTexts.ariaLabel} - data-test-subj="documentsDropdown" - /> - - - ); -}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts index 435d0ed66c4b0..d476202aa43bb 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts @@ -4,11 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - ManageProcessorForm, - ManageProcessorFormOnSubmitArg, - OnSubmitHandler, -} from './manage_processor_form'; +export { ProcessorForm, ProcessorFormOnSubmitArg, OnSubmitHandler } from './processor_form'; export { ProcessorsTree, ProcessorInfo, OnActionHandler } from './processors_tree'; @@ -22,6 +18,4 @@ export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json'; export { TestPipelineActions } from './test_pipeline'; -export { DocumentsDropdown } from './documents_dropdown'; - export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx deleted file mode 100644 index 083529921b0a7..0000000000000 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx +++ /dev/null @@ -1,74 +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, { FunctionComponent, useCallback, useEffect } from 'react'; - -import { useForm, OnFormUpdateArg, FormData, useKibana } from '../../../../../shared_imports'; -import { ProcessorInternal } from '../../types'; - -import { ManageProcessorForm as ViewComponent } from './manage_processor_form'; - -export type ManageProcessorFormOnSubmitArg = Omit; - -export type OnSubmitHandler = (processor: ManageProcessorFormOnSubmitArg) => void; - -export type OnFormUpdateHandler = (form: OnFormUpdateArg) => void; - -interface Props { - onFormUpdate: OnFormUpdateHandler; - onSubmit: OnSubmitHandler; - isOnFailure: boolean; - onOpen: () => void; - onClose: () => void; - processor?: ProcessorInternal; -} - -export const ManageProcessorForm: FunctionComponent = ({ - processor, - onFormUpdate, - onSubmit, - ...rest -}) => { - const { services } = useKibana(); - - const handleSubmit = useCallback( - async (data: FormData, isValid: boolean) => { - if (isValid) { - const { type, customOptions, fields } = data; - onSubmit({ - type, - options: customOptions ? customOptions : fields, - }); - } - }, - [onSubmit] - ); - - const maybeProcessorOptions = processor?.options; - const { form } = useForm({ - defaultValue: { fields: maybeProcessorOptions ?? {} }, - onSubmit: handleSubmit, - }); - - useEffect(() => { - const subscription = form.subscribe(onFormUpdate); - return subscription.unsubscribe; - - // TODO: Address this issue - // For some reason adding `form` object to the dependencies array here is causing an - // infinite update loop. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [onFormUpdate]); - - return ( - - ); -}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.tsx deleted file mode 100644 index ee8ca71e58446..0000000000000 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.tsx +++ /dev/null @@ -1,236 +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 { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { FunctionComponent, memo, useEffect, useState } from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlyout, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiTabs, - EuiTab, - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, -} from '@elastic/eui'; - -import { Form, FormDataProvider, FormHook } from '../../../../../shared_imports'; -import { ProcessorInternal } from '../../types'; -import { useTestPipelineContext } from '../../context'; -import { getProcessorDescriptor } from '../shared'; - -import { ProcessorSettingsFields } from './processor_settings_fields'; -import { DocumentationButton } from './documentation_button'; -import { ProcessorOutput } from './processor_output'; - -export interface Props { - isOnFailure: boolean; - processor?: ProcessorInternal; - form: FormHook; - onClose: () => void; - onOpen: () => void; - esDocsBasePath: string; -} - -const updateButtonLabel = i18n.translate( - 'xpack.ingestPipelines.settingsFormOnFailureFlyout.updateButtonLabel', - { defaultMessage: 'Update' } -); - -const addButtonLabel = i18n.translate( - 'xpack.ingestPipelines.settingsFormOnFailureFlyout.addButtonLabel', - { defaultMessage: 'Add' } -); - -const cancelButtonLabel = i18n.translate( - 'xpack.ingestPipelines.settingsFormOnFailureFlyout.cancelButtonLabel', - { defaultMessage: 'Cancel' } -); - -export type TabType = 'configuration' | 'output'; - -interface Tab { - id: TabType; - name: string; -} - -const tabs: Tab[] = [ - { - id: 'configuration', - name: i18n.translate( - 'xpack.ingestPipelines.settingsFormOnFailureFlyout.configurationTabTitle', - { - defaultMessage: 'Configuration', - } - ), - }, - { - id: 'output', - name: i18n.translate('xpack.ingestPipelines.settingsFormOnFailureFlyout.outputTabTitle', { - defaultMessage: 'Output', - }), - }, -]; - -const getFlyoutTitle = (isOnFailure: boolean, isExistingProcessor: boolean) => { - if (isExistingProcessor) { - return isOnFailure ? ( - - ) : ( - - ); - } - - return isOnFailure ? ( - - ) : ( - - ); -}; - -export const ManageProcessorForm: FunctionComponent = memo( - ({ processor, form, isOnFailure, onClose, onOpen, esDocsBasePath }) => { - const { testPipelineData, setCurrentTestPipelineData } = useTestPipelineContext(); - const { - testOutputPerProcessor, - config: { selectedDocumentIndex, documents }, - } = testPipelineData; - - const processorOutput = - processor && - testOutputPerProcessor && - testOutputPerProcessor[selectedDocumentIndex][processor.id]; - - const updateSelectedDocument = (index: number) => { - setCurrentTestPipelineData({ - type: 'updateActiveDocument', - payload: { - config: { - selectedDocumentIndex: index, - }, - }, - }); - }; - - useEffect( - () => { - onOpen(); - }, - [] /* eslint-disable-line react-hooks/exhaustive-deps */ - ); - - const [activeTab, setActiveTab] = useState('configuration'); - - let flyoutContent: React.ReactNode; - - if (activeTab === 'output') { - flyoutContent = ( - - ); - } else { - flyoutContent = ; - } - - return ( -
- - - - -
- -

{getFlyoutTitle(isOnFailure, Boolean(processor))}

-
-
-
- - - {({ type }) => { - const formDescriptor = getProcessorDescriptor(type as any); - - if (formDescriptor) { - return ( - - ); - } - return null; - }} - - -
-
- - {processor ? ( - <> - - {tabs.map((tab) => ( - { - setActiveTab(tab.id); - }} - isSelected={tab.id === activeTab} - key={tab.id} - data-test-subj={`${tab.id}Tab`} - disabled={ - (tab.id === 'output' && Boolean(testOutputPerProcessor) === false) || - Boolean(processorOutput) === false - } - > - {tab.name} - - ))} - - - - ) : undefined} - - {flyoutContent} - - - - - {cancelButtonLabel} - - - - {processor ? updateButtonLabel : addButtonLabel} - - - - -
-
- ); - }, - (previous, current) => { - return previous.processor === current.processor; - } -); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx deleted file mode 100644 index c30fdad969b24..0000000000000 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx +++ /dev/null @@ -1,217 +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 { i18n } from '@kbn/i18n'; - -import { - EuiAccordion, - EuiCallOut, - EuiCodeBlock, - EuiText, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, -} from '@elastic/eui'; - -import { ProcessorResult, Document } from '../../types'; -import { DocumentsDropdown } from '../documents_dropdown'; - -export interface Props { - processorOutput?: ProcessorResult; - documents: Document[]; - selectedDocumentIndex: number; - updateSelectedDocument: (index: number) => void; -} - -const i18nTexts = { - noOutputCalloutTitle: i18n.translate( - 'xpack.ingestPipelines.processorOutput.noOutputCalloutTitle', - { - defaultMessage: 'Unable to load the processor output.', - } - ), - tabDescription: i18n.translate('xpack.ingestPipelines.processorOutput.descriptionText', { - defaultMessage: - 'View how the processor affects the ingest document as it passes through the pipeline.', - }), - skippedCalloutTitle: i18n.translate('xpack.ingestPipelines.processorOutput.skippedCalloutTitle', { - defaultMessage: 'The processor was not run.', - }), - droppedCalloutTitle: i18n.translate('xpack.ingestPipelines.processorOutput.droppedCalloutTitle', { - defaultMessage: 'The document was dropped.', - }), - processorOutputLabel: i18n.translate( - 'xpack.ingestPipelines.processorOutput.processorOutputCodeBlockLabel', - { - defaultMessage: 'Processor output', - } - ), - processorErrorLabel: i18n.translate( - 'xpack.ingestPipelines.processorOutput.processorErrorCodeBlockLabel', - { - defaultMessage: 'Processor error', - } - ), - prevProcessorLabel: i18n.translate( - 'xpack.ingestPipelines.processorOutput.previousOutputCodeBlockLabel', - { - defaultMessage: 'View previous processor output', - } - ), - processorIgnoredErrorLabel: i18n.translate( - 'xpack.ingestPipelines.processorOutput.ignoredErrorCodeBlockLabel', - { - defaultMessage: 'View ignored error', - } - ), -}; - -export const ProcessorOutput: React.FunctionComponent = ({ - processorOutput, - documents, - selectedDocumentIndex, - updateSelectedDocument, -}) => { - // This code should not be reached, - // but if for some reason the output is undefined, we render a callout message - if (!processorOutput) { - return ; - } - - const { - prevProcessorResult, - doc: currentResult, - ignored_error: ignoredError, - error, - status, - } = processorOutput!; - - return ( -
- -

{i18nTexts.tabDescription}

-
- - {/* There is no output for "skipped" status, so we render an info callout */} - {status === 'skipped' && ( - <> - - - - )} - - {/* There is no output for "dropped status", so we render a warning callout */} - {status === 'dropped' && ( - <> - - - - )} - - {currentResult && ( - <> - - - - -

{i18nTexts.processorOutputLabel}

-
- - - -
- - - - - {JSON.stringify(currentResult, null, 2)} - - - )} - - {error && ( - <> - - - - -

{i18nTexts.processorErrorLabel}

-
- - - -
- - - - - {JSON.stringify(error, null, 2)} - - - )} - - {prevProcessorResult?.doc && ( - <> - - - -

{i18nTexts.prevProcessorLabel}

- - } - > - <> - - - - {JSON.stringify(prevProcessorResult.doc, null, 2)} - - -
- - )} - - {ignoredError && ( - <> - - - -

{i18nTexts.processorIgnoredErrorLabel}

- - } - > - <> - - - - {JSON.stringify(ignoredError, null, 2)} - - -
- - )} -
- ); -}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss index d9c3d84eec082..55630fa96d9b0 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss @@ -63,6 +63,6 @@ &__statusContainer { // Prevent content jump when spinner renders - min-width: 12px; + min-width: 15px; } } diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx index a58d482022b4d..08d456b47180c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx @@ -6,11 +6,12 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiToolTip, EuiIcon } from '@elastic/eui'; +import { EuiToolTip, EuiIcon, IconType } from '@elastic/eui'; import { ProcessorStatus } from '../types'; +import { ErrorIcon, ErrorIgnoredIcon, SkippedIcon } from './shared'; interface ProcessorStatusIcon { - icon: string; + icon: IconType; iconColor: string; label: string; } @@ -24,28 +25,28 @@ const processorStatusToIconMap: Record = { }), }, error: { - icon: 'crossInACircleFilled', + icon: ErrorIcon, iconColor: 'danger', label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.errorStatusAriaLabel', { defaultMessage: 'Error', }), }, error_ignored: { - icon: 'alert', - iconColor: 'warning', + icon: ErrorIgnoredIcon, + iconColor: 'danger', label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.errorIgnoredStatusAriaLabel', { defaultMessage: 'Error ignored', }), }, dropped: { - icon: 'alert', - iconColor: 'warning', + icon: 'indexClose', + iconColor: 'subdued', label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.droppedStatusAriaLabel', { defaultMessage: 'Dropped', }), }, skipped: { - icon: 'dot', + icon: SkippedIcon, iconColor: 'subdued', label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.skippedStatusAriaLabel', { defaultMessage: 'Skipped', @@ -53,7 +54,7 @@ const processorStatusToIconMap: Record = { }, inactive: { icon: 'dot', - iconColor: 'subdued', + iconColor: '#D3DAE6', // $euiColorLightShade label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.inactiveStatusAriaLabel', { defaultMessage: 'Not run', }), @@ -64,7 +65,7 @@ const processorStatusToIconMap: Record = { // This is not expected and likely means we need to modify the code to support a new status const unknownStatus = { icon: 'dot', - iconColor: 'subdued', + iconColor: '#D3DAE6', // $euiColorLightShade label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.unknownStatusAriaLabel', { defaultMessage: 'Unknown', }), diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx new file mode 100644 index 0000000000000..5231a3d17811b --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx @@ -0,0 +1,134 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import React, { FunctionComponent, useEffect } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; + +import { Form, FormDataProvider, FormHook } from '../../../../../shared_imports'; +import { getProcessorDescriptor } from '../shared'; + +import { DocumentationButton } from './documentation_button'; +import { ProcessorSettingsFields } from './processor_settings_fields'; + +interface Fields { + fields: { [key: string]: any }; +} +export interface Props { + isOnFailure: boolean; + form: FormHook; + onOpen: () => void; + esDocsBasePath: string; + closeFlyout: () => void; + handleSubmit: (shouldCloseFlyout?: boolean) => Promise; +} + +const addButtonLabel = i18n.translate( + 'xpack.ingestPipelines.addProcessorFormOnFailureFlyout.addButtonLabel', + { defaultMessage: 'Add' } +); + +const cancelButtonLabel = i18n.translate( + 'xpack.ingestPipelines.addProcesorFormOnFailureFlyout.cancelButtonLabel', + { defaultMessage: 'Cancel' } +); + +const getFlyoutTitle = (isOnFailure: boolean) => { + return isOnFailure ? ( + + ) : ( + + ); +}; + +export const AddProcessorForm: FunctionComponent = ({ + isOnFailure, + onOpen, + form, + esDocsBasePath, + closeFlyout, + handleSubmit, +}) => { + useEffect( + () => { + onOpen(); + }, + [] /* eslint-disable-line react-hooks/exhaustive-deps */ + ); + + return ( +
+ + + + +
+ +

{getFlyoutTitle(isOnFailure)}

+
+
+
+ + + {({ type }) => { + const formDescriptor = getProcessorDescriptor(type as any); + + if (formDescriptor) { + return ( + + ); + } + return null; + }} + + +
+
+ + + + + + + {cancelButtonLabel} + + + { + await handleSubmit(); + }} + > + {addButtonLabel} + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/documentation_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/documentation_button.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/documentation_button.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/documentation_button.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx new file mode 100644 index 0000000000000..e449ed75b6343 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx @@ -0,0 +1,253 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import React, { FunctionComponent, useEffect, useState } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiTabs, + EuiTab, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; + +import { Form, FormDataProvider, FormHook } from '../../../../../shared_imports'; +import { ProcessorInternal } from '../../types'; +import { useTestPipelineContext } from '../../context'; +import { getProcessorDescriptor } from '../shared'; + +import { ProcessorSettingsFields } from './processor_settings_fields'; +import { DocumentationButton } from './documentation_button'; +import { ProcessorOutput } from './processor_output'; +import { Fields } from './processor_form.container'; + +export interface Props { + isOnFailure: boolean; + form: FormHook; + onOpen: () => void; + esDocsBasePath: string; + closeFlyout: () => void; + resetProcessors: () => void; + handleSubmit: (shouldCloseFlyout?: boolean) => Promise; + getProcessor: () => ProcessorInternal; +} + +const updateButtonLabel = i18n.translate( + 'xpack.ingestPipelines.processorFormFlyout.updateButtonLabel', + { defaultMessage: 'Update' } +); + +const cancelButtonLabel = i18n.translate( + 'xpack.ingestPipelines.processorFormFlyout.cancelButtonLabel', + { defaultMessage: 'Cancel' } +); + +export type TabType = 'configuration' | 'output'; + +interface Tab { + id: TabType; + name: string; +} + +const tabs: Tab[] = [ + { + id: 'configuration', + name: i18n.translate( + 'xpack.ingestPipelines.settingsFormOnFailureFlyout.configurationTabTitle', + { + defaultMessage: 'Configuration', + } + ), + }, + { + id: 'output', + name: i18n.translate('xpack.ingestPipelines.settingsFormOnFailureFlyout.outputTabTitle', { + defaultMessage: 'Output', + }), + }, +]; + +const getFlyoutTitle = (isOnFailure: boolean) => { + return isOnFailure ? ( + + ) : ( + + ); +}; + +export const EditProcessorForm: FunctionComponent = ({ + getProcessor, + form, + isOnFailure, + onOpen, + esDocsBasePath, + closeFlyout, + handleSubmit, + resetProcessors, +}) => { + const { testPipelineData, setCurrentTestPipelineData } = useTestPipelineContext(); + const { + testOutputPerProcessor, + config: { selectedDocumentIndex, documents }, + isExecutingPipeline, + } = testPipelineData; + + const processor = getProcessor(); + + const processorOutput = + processor && + testOutputPerProcessor && + testOutputPerProcessor[selectedDocumentIndex][processor.id]; + + const updateSelectedDocument = (index: number) => { + setCurrentTestPipelineData({ + type: 'updateActiveDocument', + payload: { + config: { + selectedDocumentIndex: index, + }, + }, + }); + }; + + useEffect( + () => { + onOpen(); + }, + [] /* eslint-disable-line react-hooks/exhaustive-deps */ + ); + + const [activeTab, setActiveTab] = useState('configuration'); + + let flyoutContent: React.ReactNode; + + if (activeTab === 'output') { + flyoutContent = ( + + ); + } else { + flyoutContent = ; + } + + return ( +
+ { + resetProcessors(); + closeFlyout(); + }} + > + + + +
+ +

{getFlyoutTitle(isOnFailure)}

+
+
+
+ + + {({ type }) => { + const formDescriptor = getProcessorDescriptor(type as any); + + if (formDescriptor) { + return ( + + ); + } + return null; + }} + + +
+
+ + + {tabs.map((tab) => ( + { + if (tab.id === 'output') { + await handleSubmit(false); + } else { + form.reset({ defaultValue: { fields: processor.options } }); + } + setActiveTab(tab.id); + }} + isSelected={tab.id === activeTab} + key={tab.id} + data-test-subj={`${tab.id}Tab`} + disabled={ + tab.id === 'output' && + (Boolean(testOutputPerProcessor) === false || Boolean(processorOutput) === false) + } + > + {tab.name} + + ))} + + + + + {flyoutContent} + + + + + { + resetProcessors(); + closeFlyout(); + }} + > + {cancelButtonLabel} + + + + { + if (activeTab === 'output') { + return closeFlyout(); + } + await handleSubmit(); + }} + > + {updateButtonLabel} + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/index.ts similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/index.ts rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/index.ts diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/text_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/text_editor.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/xjson_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/xjson_editor.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/xjson_editor.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/xjson_editor.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/index.ts similarity index 71% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/index.ts rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/index.ts index 986bd52e911bf..5a8d2522f1376 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/index.ts @@ -5,7 +5,7 @@ */ export { - ManageProcessorForm, - ManageProcessorFormOnSubmitArg, + ProcessorFormContainer as ProcessorForm, + ProcessorFormOnSubmitArg, OnSubmitHandler, -} from './manage_processor_form.container'; +} from './processor_form.container'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx new file mode 100644 index 0000000000000..332908d0756f2 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent, useCallback, useEffect, useRef } from 'react'; + +import { useForm, OnFormUpdateArg, FormData, useKibana } from '../../../../../shared_imports'; +import { ProcessorInternal } from '../../types'; + +import { EditProcessorForm } from './edit_processor_form'; +import { AddProcessorForm } from './add_processor_form'; + +export type ProcessorFormOnSubmitArg = Omit; + +export type OnSubmitHandler = (processor: ProcessorFormOnSubmitArg) => void; + +export type OnFormUpdateHandler = (form: OnFormUpdateArg) => void; + +export interface Fields { + fields: { [key: string]: any }; +} + +interface Props { + onFormUpdate: OnFormUpdateHandler; + onSubmit: OnSubmitHandler; + isOnFailure: boolean; + onOpen: () => void; + onClose: () => void; + processor?: ProcessorInternal; +} + +export const ProcessorFormContainer: FunctionComponent = ({ + processor, + onFormUpdate, + onSubmit, + onClose, + ...rest +}) => { + const { services } = useKibana(); + + // We need to keep track of the processor form state if the user + // has made config changes, navigated between tabs (Configuration vs. Output) + // and has not yet submitted the form + const unsavedFormState = useRef(); + + const getProcessor = useCallback((): ProcessorInternal => { + let options; + + if (unsavedFormState?.current) { + options = unsavedFormState.current; + } else { + options = processor?.options ?? {}; + } + + return { ...processor, options } as ProcessorInternal; + }, [processor, unsavedFormState]); + + const { form } = useForm({ + defaultValue: { fields: getProcessor().options }, + }); + + const handleSubmit = useCallback( + async (shouldCloseFlyout: boolean = true) => { + const { isValid, data } = await form.submit(); + + if (isValid) { + const { type, customOptions, fields } = data as FormData; + const options = customOptions ? customOptions : fields; + + unsavedFormState.current = options; + + onSubmit({ + type, + options, + }); + + if (shouldCloseFlyout) { + onClose(); + } + } + }, + [form, onClose, onSubmit] + ); + + const resetProcessors = useCallback(() => { + onSubmit({ + type: processor!.type, + options: processor?.options || {}, + }); + }, [onSubmit, processor]); + + useEffect(() => { + const subscription = form.subscribe(onFormUpdate); + return subscription.unsubscribe; + + // TODO: Address this issue + // For some reason adding `form` object to the dependencies array here is causing an + // infinite update loop. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [onFormUpdate]); + + if (processor) { + return ( + + ); + } else { + return ( + + ); + } +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/index.ts new file mode 100644 index 0000000000000..3b506fc9296e3 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ProcessorOutput } from './processor_output'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.scss new file mode 100644 index 0000000000000..e1b5eb83584ff --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.scss @@ -0,0 +1,12 @@ +.processorOutput { + &__callOut { + &--customIcon { + .euiCallOutHeader { + align-items: center; + } + } + &__codeBlock > pre { + background: transparent; + } + } +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.tsx new file mode 100644 index 0000000000000..bd0ce6ca2cd52 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.tsx @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiAccordion, + EuiCallOut, + EuiCodeBlock, + EuiText, + EuiSpacer, + EuiSelect, +} from '@elastic/eui'; + +import { SectionLoading } from '../../../../../../shared_imports'; +import { ProcessorResult, Document } from '../../../types'; +import { ErrorIcon, ErrorIgnoredIcon, SkippedIcon } from '../../shared'; + +import './processor_output.scss'; + +export interface Props { + processorOutput?: ProcessorResult; + documents: Document[]; + selectedDocumentIndex: number; + updateSelectedDocument: (index: number) => void; + isExecuting?: boolean; +} + +const i18nTexts = { + tabDescription: i18n.translate('xpack.ingestPipelines.processorOutput.descriptionText', { + defaultMessage: + 'View how the processor affects the ingest document as it passes through the pipeline.', + }), + skippedCalloutTitle: i18n.translate('xpack.ingestPipelines.processorOutput.skippedCalloutTitle', { + defaultMessage: 'The processor was not run.', + }), + droppedCalloutTitle: i18n.translate('xpack.ingestPipelines.processorOutput.droppedCalloutTitle', { + defaultMessage: 'The document was dropped.', + }), + noOutputCalloutTitle: i18n.translate( + 'xpack.ingestPipelines.processorOutput.noOutputCalloutTitle', + { + defaultMessage: 'Output is not available for this processor.', + } + ), + processorOutputLabel: i18n.translate( + 'xpack.ingestPipelines.processorOutput.processorOutputCodeBlockLabel', + { + defaultMessage: 'Data out', + } + ), + processorErrorTitle: i18n.translate( + 'xpack.ingestPipelines.processorOutput.processorErrorCodeBlockLabel', + { + defaultMessage: 'There was an error', + } + ), + prevProcessorLabel: i18n.translate( + 'xpack.ingestPipelines.processorOutput.processorInputCodeBlockLabel', + { + defaultMessage: 'Data in', + } + ), + processorIgnoredErrorTitle: i18n.translate( + 'xpack.ingestPipelines.processorOutput.ignoredErrorCodeBlockLabel', + { + defaultMessage: 'There was an error that was ignored', + } + ), + documentsDropdownLabel: i18n.translate( + 'xpack.ingestPipelines.processorOutput.documentsDropdownLabel', + { + defaultMessage: 'Test data:', + } + ), + loadingMessage: i18n.translate('xpack.ingestPipelines.processorOutput.loadingMessage', { + defaultMessage: 'Loading processor output…', + }), +}; + +export const ProcessorOutput: FunctionComponent = ({ + processorOutput, + documents, + selectedDocumentIndex, + updateSelectedDocument, + isExecuting, +}) => { + if (isExecuting) { + return {i18nTexts.loadingMessage}; + } + + if (!processorOutput) { + return ; + } + + const { + processorInput, + doc: currentResult, + ignored_error: ignoredError, + error, + status, + } = processorOutput!; + + const NoOutputCallOut: FunctionComponent = () => ( + + ); + + const getOutputContent = () => { + switch (status) { + case 'skipped': + return ( + + ); + case 'dropped': + return ; + case 'success': + if (currentResult) { + return ( + + {JSON.stringify(currentResult, null, 2)} + + ); + } + + return ; + case 'error': + return ( + + + {JSON.stringify(error, null, 2)} + + + ); + case 'error_ignored': + return ( + + + {JSON.stringify(ignoredError, null, 2)} + + + ); + default: + return ; + } + }; + + return ( +
+ +

{i18nTexts.tabDescription}

+
+ + + + {/* Documents dropdown */} + ({ + value: index, + text: i18n.translate('xpack.ingestPipelines.processorOutput.documentLabel', { + defaultMessage: 'Document {number}', + values: { + number: index + 1, + }, + }), + }))} + value={selectedDocumentIndex} + onChange={(e) => { + updateSelectedDocument(Number(e.target.value)); + }} + aria-label={i18nTexts.documentsDropdownLabel} + prepend={i18nTexts.documentsDropdownLabel} + /> + + + + {/* Data-in accordion */} + +

{i18nTexts.prevProcessorLabel}

+ + } + > + <> + + + + {/* If there is no processorInput defined (i.e., it's the first processor), we provide the sample document */} + {JSON.stringify( + processorInput ? processorInput : documents[selectedDocumentIndex], + null, + 2 + )} + + +
+ + + + {/* Data-out content */} + + {i18nTexts.processorOutputLabel} + + + + + {getOutputContent()} +
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_settings_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_settings_fields.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_settings_fields.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_settings_fields.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/append.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/append.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/append.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/append.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/bytes.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/bytes.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/bytes.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/bytes.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/circle.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/circle.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/circle.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/circle.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/common_processor_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/common_processor_fields.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/common_processor_fields.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/common_processor_fields.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/field_name_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/field_name_field.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/field_name_field.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/field_name_field.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/ignore_missing_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/ignore_missing_field.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/ignore_missing_field.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/ignore_missing_field.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/index.ts similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/index.ts rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/index.ts diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/processor_type_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/processor_type_field.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/processor_type_field.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/processor_type_field.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/properties_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/properties_field.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/properties_field.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/properties_field.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/target_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/target_field.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/target_field.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/target_field.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/convert.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/convert.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/convert.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/convert.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/csv.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/csv.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/csv.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/csv.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/custom.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/custom.tsx similarity index 96% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/custom.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/custom.tsx index c2aab62cf8933..f49e77501f931 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/custom.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/custom.tsx @@ -17,6 +17,7 @@ import { const { emptyField, isJsonField } = fieldValidators; import { XJsonEditor } from '../field_components'; +import { Fields } from '../processor_form.container'; import { EDITOR_PX_HEIGHT } from './shared'; const customConfig: FieldConfig = { @@ -60,7 +61,7 @@ const customConfig: FieldConfig = { }; interface Props { - defaultOptions?: any; + defaultOptions?: Fields['fields']; } /** diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/date.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/date.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/date.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/date.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/date_index_name.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/date_index_name.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/date_index_name.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/date_index_name.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/dissect.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/dissect.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/dissect.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/dissect.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/dot_expander.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/dot_expander.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/dot_expander.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/dot_expander.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/drop.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/drop.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/drop.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/drop.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/enrich.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/enrich.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/enrich.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/enrich.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/fail.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/fail.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/fail.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/fail.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/foreach.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/foreach.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/foreach.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/foreach.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/geoip.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/geoip.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/geoip.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/geoip.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/grok.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/grok.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/grok.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/grok.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/gsub.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/gsub.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/gsub.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/gsub.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/html_strip.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/html_strip.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/html_strip.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/html_strip.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/index.ts similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/index.ts rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/index.ts diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/inference.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/inference.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/inference.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/inference.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/join.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/join.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/join.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/join.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/json.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/json.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/json.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/json.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/kv.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/kv.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/kv.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/kv.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/lowercase.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/lowercase.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/lowercase.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/lowercase.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/pipeline.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/pipeline.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/pipeline.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/pipeline.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/remove.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/remove.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/remove.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/remove.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/rename.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/rename.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/rename.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/rename.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/script.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/script.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/script.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/script.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/set.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/set.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/set.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/set.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/set_security_user.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/set_security_user.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/set_security_user.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/set_security_user.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/shared.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/shared.ts similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/shared.ts rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/shared.ts diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/sort.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/sort.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/sort.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/sort.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/split.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/split.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/split.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/split.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/trim.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/trim.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/trim.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/trim.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/uppercase.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/uppercase.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/uppercase.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/uppercase.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/url_decode.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/url_decode.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/url_decode.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/url_decode.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/user_agent.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/user_agent.tsx similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/user_agent.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/user_agent.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/index.ts index 1b4b975b5305e..3f258bf279e42 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/index.ts @@ -9,3 +9,5 @@ export { mapProcessorTypeToDescriptor, ProcessorType, } from './map_processor_type_to_form'; + +export { ErrorIcon, ErrorIgnoredIcon, SkippedIcon } from './status_icons'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx index 95a8d35c119a6..8d9260f3c822c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx @@ -46,7 +46,7 @@ import { UrlDecode, UserAgent, FormFieldsComponent, -} from '../manage_processor_form/processors'; +} from '../processor_form/processors'; interface FieldDescriptor { FieldsComponent?: FormFieldsComponent; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_icon.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_icon.tsx new file mode 100644 index 0000000000000..58cb56d4f768d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_icon.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; + +export const ErrorIcon: FunctionComponent = () => ( + + + +); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_ignored_icon.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_ignored_icon.tsx new file mode 100644 index 0000000000000..74ceda7687f02 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_ignored_icon.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; + +export const ErrorIgnoredIcon: FunctionComponent = () => ( + + + + +); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/index.ts new file mode 100644 index 0000000000000..9fe0871e445eb --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/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 { ErrorIcon } from './error_icon'; +export { ErrorIgnoredIcon } from './error_ignored_icon'; +export { SkippedIcon } from './skipped_icon'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/skipped_icon.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/skipped_icon.tsx new file mode 100644 index 0000000000000..c540bd3790fb0 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/skipped_icon.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; + +export const SkippedIcon: FunctionComponent = () => ( + + + +); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/add_documents_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/add_documents_button.tsx index e3ef9a9ee5390..26492454cbcf5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/add_documents_button.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/add_documents_button.tsx @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import React, { FunctionComponent } from 'react'; import { EuiButtonEmpty } from '@elastic/eui'; +import { TestPipelineFlyoutTab } from './test_pipeline_flyout_tabs'; const i18nTexts = { buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.buttonLabel', { @@ -14,16 +15,15 @@ const i18nTexts = { }; interface Props { - openTestPipelineFlyout: () => void; + openFlyout: (activeFlyoutTab: TestPipelineFlyoutTab) => void; } -export const AddDocumentsButton: FunctionComponent = ({ openTestPipelineFlyout }) => { +export const AddDocumentsButton: FunctionComponent = ({ openFlyout }) => { return ( openFlyout('documents')} data-test-subj="addDocumentsButton" - iconType="plusInCircleFilled" > {i18nTexts.buttonLabel} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.scss new file mode 100644 index 0000000000000..5deb48a2f01a7 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.scss @@ -0,0 +1,3 @@ +.documentsDropdownPanel { + min-width: 200px; +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx new file mode 100644 index 0000000000000..269a697a33c17 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import React, { FunctionComponent, useState } from 'react'; +import { + EuiButton, + EuiPopover, + EuiButtonEmpty, + EuiPopoverTitle, + EuiSelectable, + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; + +import { Document } from '../../../types'; + +import { TestPipelineFlyoutTab } from '../test_pipeline_flyout_tabs'; + +import './documents_dropdown.scss'; + +const i18nTexts = { + dropdownLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsdropdown.dropdownLabel', + { + defaultMessage: 'Documents:', + } + ), + addDocumentsButtonLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsDropdown.buttonLabel', + { + defaultMessage: 'Add documents', + } + ), + popoverTitle: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsDropdown.popoverTitle', + { + defaultMessage: 'Test documents', + } + ), +}; + +interface Props { + documents: Document[]; + selectedDocumentIndex: number; + updateSelectedDocument: (index: number) => void; + openFlyout: (activeFlyoutTab: TestPipelineFlyoutTab) => void; +} + +export const DocumentsDropdown: FunctionComponent = ({ + documents, + selectedDocumentIndex, + updateSelectedDocument, + openFlyout, +}) => { + const [showPopover, setShowPopover] = useState(false); + + const managePipelineButton = ( + setShowPopover((previousBool) => !previousBool)} + iconType="arrowDown" + iconSide="right" + > + {i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.selectedDocumentLabel', { + defaultMessage: 'Document {selectedDocument}', + values: { + selectedDocument: selectedDocumentIndex + 1, + }, + })} + + ); + + return ( + setShowPopover(false)} + button={managePipelineButton} + panelPaddingSize="none" + withTitle + repositionOnScroll + data-test-subj="documentsDropdown" + panelClassName="documentsDropdownPanel" + > + ({ + key: index.toString(), + checked: selectedDocumentIndex === index ? 'on' : undefined, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.documentLabel', { + defaultMessage: 'Document {documentNumber}', + values: { + documentNumber: index + 1, + }, + }), + }))} + onChange={(newOptions) => { + const selectedOption = newOptions.find((option) => option.checked === 'on'); + if (selectedOption) { + updateSelectedDocument(Number(selectedOption.key!)); + } + + setShowPopover(false); + }} + > + {(list, search) => ( +
+ {i18nTexts.popoverTitle} + {list} +
+ )} +
+ + + + + + { + openFlyout('documents'); + setShowPopover(false); + }} + data-test-subj="addDocumentsButton" + > + {i18nTexts.addDocumentsButtonLabel} + + + + + +
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/index.ts similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/index.ts rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/index.ts diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx index 6fd1adad54f84..9018042229590 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx @@ -5,7 +5,8 @@ */ import { i18n } from '@kbn/i18n'; import React, { FunctionComponent } from 'react'; -import { EuiButton, EuiToolTip } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; +import { TestPipelineFlyoutTab } from './test_pipeline_flyout_tabs'; const i18nTexts = { buttonLabel: i18n.translate( @@ -14,46 +15,15 @@ const i18nTexts = { defaultMessage: 'View output', } ), - disabledButtonTooltipLabel: i18n.translate( - 'xpack.ingestPipelines.pipelineEditor.testPipeline.outputButtonTooltipLabel', - { - defaultMessage: 'Add documents to view the output', - } - ), }; interface Props { - isDisabled: boolean; - openTestPipelineFlyout: () => void; + openFlyout: (activeFlyoutTab: TestPipelineFlyoutTab) => void; } -export const TestOutputButton: FunctionComponent = ({ - isDisabled, - openTestPipelineFlyout, -}) => { - if (isDisabled) { - return ( - {i18nTexts.disabledButtonTooltipLabel}

}> - - {i18nTexts.buttonLabel} - -
- ); - } - +export const TestOutputButton: FunctionComponent = ({ openFlyout }) => { return ( - + openFlyout('output')} data-test-subj="viewOutputButton"> {i18nTexts.buttonLabel} ); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_actions.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_actions.tsx index eb9d9352e4b90..cec02db26729d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_actions.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_actions.tsx @@ -4,15 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { FunctionComponent, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useTestPipelineContext, usePipelineProcessorsContext } from '../../context'; - -import { DocumentsDropdown } from '../documents_dropdown'; +import { DocumentsDropdown } from './documents_dropdown'; import { TestPipelineFlyoutTab } from './test_pipeline_flyout_tabs'; import { AddDocumentsButton } from './add_documents_button'; import { TestOutputButton } from './test_output_button'; -import { TestPipelineFlyout } from './test_pipeline_flyout'; +import { TestPipelineFlyout } from './test_pipeline_flyout.container'; + +const i18nTexts = { + testPipelineActionsLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.testPipeline.testPipelineActionsLabel', + { + defaultMessage: 'Test pipeline:', + } + ), +}; export const TestPipelineActions: FunctionComponent = () => { const { testPipelineData, setCurrentTestPipelineData } = useTestPipelineContext(); @@ -40,35 +49,42 @@ export const TestPipelineActions: FunctionComponent = () => { }); }; + const openFlyout = (activeTab: TestPipelineFlyoutTab) => { + setOpenTestPipelineFlyout(true); + setActiveFlyoutTab(activeTab); + }; + return ( <> - + + + + + {i18nTexts.testPipelineActionsLabel} + + + + {documents ? ( ) : ( - { - setOpenTestPipelineFlyout(true); - setActiveFlyoutTab('documents'); - }} - /> + )} - - { - setOpenTestPipelineFlyout(true); - setActiveFlyoutTab('output'); - }} - /> - + + {testOutputPerProcessor && ( + + + + )} + {openTestPipelineFlyout && ( void; + processors: DeserializeResult; +} + +export interface TestPipelineConfig { + documents: Document[]; + verbose?: boolean; +} + +export const TestPipelineFlyout: React.FunctionComponent = ({ + onClose, + activeTab, + processors, +}) => { + const { services } = useKibana(); + + const { + testPipelineData, + setCurrentTestPipelineData, + updateTestOutputPerProcessor, + } = useTestPipelineContext(); + + const { + config: { documents: cachedDocuments, verbose: cachedVerbose }, + } = testPipelineData; + + const { form } = useForm({ + schema: documentsSchema, + defaultValue: { + documents: cachedDocuments || '', + }, + }); + + const [selectedTab, setSelectedTab] = useState(activeTab); + + const [isRunningTest, setIsRunningTest] = useState(false); + const [testingError, setTestingError] = useState(null); + const [testOutput, setTestOutput] = useState(undefined); + + const handleTestPipeline = useCallback( + async ( + { documents, verbose }: TestPipelineConfig, + updateProcessorOutput?: boolean + ): Promise<{ isSuccessful: boolean }> => { + const serializedProcessors = serialize({ pipeline: processors }); + + setIsRunningTest(true); + setTestingError(null); + + const { error, data: currentTestOutput } = await services.api.simulatePipeline({ + documents, + verbose, + pipeline: { ...serializedProcessors }, + }); + + setIsRunningTest(false); + + if (error) { + setTestingError(error); + + // reset the per-processor output + // this is needed in the scenario where the pipeline has already executed, + // but you modified the sample documents and there was an error on re-execution + setCurrentTestPipelineData({ + type: 'updateOutputPerProcessor', + payload: { + isExecutingPipeline: false, + testOutputPerProcessor: undefined, + }, + }); + + return { isSuccessful: false }; + } + + setCurrentTestPipelineData({ + type: 'updateConfig', + payload: { + config: { + documents, + verbose, + }, + }, + }); + + // We sometimes need to manually refresh the per-processor output + // e.g., when clicking the "Refresh output" button and there have been no state changes + if (updateProcessorOutput) { + updateTestOutputPerProcessor(documents, processors); + } + + setTestOutput(currentTestOutput); + + services.notifications.toasts.addSuccess( + i18n.translate('xpack.ingestPipelines.testPipelineFlyout.successNotificationText', { + defaultMessage: 'Pipeline executed', + }), + { + toastLifeTimeMs: 1000, + } + ); + + return { isSuccessful: true }; + }, + [ + processors, + services.api, + services.notifications.toasts, + setCurrentTestPipelineData, + updateTestOutputPerProcessor, + ] + ); + + const validateAndTestPipeline = async () => { + const { isValid, data } = await form.submit(); + + if (!isValid) { + return; + } + + const { documents } = data as { documents: Document[] }; + + const { isSuccessful } = await handleTestPipeline({ + documents: documents!, + verbose: cachedVerbose, + }); + + if (isSuccessful) { + setSelectedTab('output'); + } + }; + + useEffect(() => { + if (cachedDocuments && activeTab === 'output') { + handleTestPipeline({ documents: cachedDocuments, verbose: cachedVerbose }, true); + } + // We only want to know on initial mount if + // there are cached documents and we are on the output tab + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx index b26c6f536366d..46271a6bce51c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useCallback, useEffect } from 'react'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { EuiFlyout, @@ -17,112 +16,46 @@ import { EuiCallOut, } from '@elastic/eui'; -import { useKibana } from '../../../../../shared_imports'; -import { useTestPipelineContext } from '../../context'; -import { serialize } from '../../serialize'; -import { DeserializeResult } from '../../deserialize'; +import { Form, FormHook } from '../../../../../shared_imports'; import { Document } from '../../types'; import { Tabs, TestPipelineFlyoutTab, OutputTab, DocumentsTab } from './test_pipeline_flyout_tabs'; export interface Props { - activeTab: TestPipelineFlyoutTab; onClose: () => void; - processors: DeserializeResult; + handleTestPipeline: ( + testPipelineConfig: TestPipelineConfig, + refreshOutputPerProcessor?: boolean + ) => Promise<{ isSuccessful: boolean }>; + isRunningTest: boolean; + cachedVerbose?: boolean; + cachedDocuments?: Document[]; + testOutput?: any; + form: FormHook; + validateAndTestPipeline: () => Promise; + selectedTab: TestPipelineFlyoutTab; + setSelectedTab: (selectedTa: TestPipelineFlyoutTab) => void; + testingError: any; } -export interface HandleTestPipelineArgs { +export interface TestPipelineConfig { documents: Document[]; verbose?: boolean; } export const TestPipelineFlyout: React.FunctionComponent = ({ + handleTestPipeline, + isRunningTest, + cachedVerbose, + cachedDocuments, + testOutput, + form, + validateAndTestPipeline, + selectedTab, + setSelectedTab, + testingError, onClose, - activeTab, - processors, }) => { - const { services } = useKibana(); - - const { - testPipelineData, - setCurrentTestPipelineData, - updateTestOutputPerProcessor, - } = useTestPipelineContext(); - - const { - config: { documents: cachedDocuments, verbose: cachedVerbose }, - } = testPipelineData; - - const [selectedTab, setSelectedTab] = useState(activeTab); - - const [shouldTestImmediately, setShouldTestImmediately] = useState(false); - const [isRunningTest, setIsRunningTest] = useState(false); - const [testingError, setTestingError] = useState(null); - const [testOutput, setTestOutput] = useState(undefined); - - const handleTestPipeline = useCallback( - async ({ documents, verbose }: HandleTestPipelineArgs) => { - const serializedProcessors = serialize({ pipeline: processors }); - - setIsRunningTest(true); - setTestingError(null); - - const { error, data: currentTestOutput } = await services.api.simulatePipeline({ - documents, - verbose, - pipeline: { ...serializedProcessors }, - }); - - setIsRunningTest(false); - - if (error) { - setTestingError(error); - return; - } - - setCurrentTestPipelineData({ - type: 'updateConfig', - payload: { - config: { - documents, - verbose, - }, - }, - }); - - setTestOutput(currentTestOutput); - - services.notifications.toasts.addSuccess( - i18n.translate('xpack.ingestPipelines.testPipelineFlyout.successNotificationText', { - defaultMessage: 'Pipeline executed', - }), - { - toastLifeTimeMs: 1000, - } - ); - - setSelectedTab('output'); - }, - [services.api, processors, setCurrentTestPipelineData, services.notifications.toasts] - ); - - useEffect(() => { - if (cachedDocuments) { - setShouldTestImmediately(true); - } - // We only want to know on initial mount if there are cached documents - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - // If the user has already tested the pipeline once, - // use the cached test config and automatically execute the pipeline - if (shouldTestImmediately) { - setShouldTestImmediately(false); - handleTestPipeline({ documents: cachedDocuments!, verbose: cachedVerbose }); - } - }, [handleTestPipeline, cachedDocuments, cachedVerbose, shouldTestImmediately]); - let tabContent; if (selectedTab === 'output') { @@ -138,13 +71,19 @@ export const TestPipelineFlyout: React.FunctionComponent = ({ } else { // default to "Documents" tab tabContent = ( - +
+ + ); } @@ -163,9 +102,17 @@ export const TestPipelineFlyout: React.FunctionComponent = ({ { + if (nextTab === 'output') { + // When switching to the output tab, + // we automatically run the pipeline if documents are defined + validateAndTestPipeline(); + } else { + form.reset({ defaultValue: { documents: cachedDocuments! } }); + setSelectedTab(nextTab); + } + }} selectedTab={selectedTab} - getIsDisabled={(tabId) => !testOutput && tabId === 'output'} /> diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx index dd12cdab0c934..b2326644340a7 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx @@ -10,67 +10,23 @@ import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiText, EuiButton, EuiLink } from '@elastic/eui'; -import { - getUseField, - Field, - JsonEditorField, - Form, - useForm, - useKibana, -} from '../../../../../../shared_imports'; - -import { TestPipelineContext } from '../../../context'; -import { Document } from '../../../types'; -import { DeserializeResult } from '../../../deserialize'; -import { HandleTestPipelineArgs } from '../test_pipeline_flyout'; -import { documentsSchema } from './documents_schema'; +import { getUseField, Field, JsonEditorField, useKibana } from '../../../../../../shared_imports'; const UseField = getUseField({ component: Field }); interface Props { - handleTestPipeline: (data: HandleTestPipelineArgs) => void; - setPerProcessorOutput: (documents: Document[] | undefined, processors: DeserializeResult) => void; + validateAndTestPipeline: () => Promise; isRunningTest: boolean; - processors: DeserializeResult; - testPipelineData: TestPipelineContext['testPipelineData']; + isSubmitButtonDisabled: boolean; } export const DocumentsTab: React.FunctionComponent = ({ - handleTestPipeline, + validateAndTestPipeline, + isSubmitButtonDisabled, isRunningTest, - setPerProcessorOutput, - processors, - testPipelineData, }) => { const { services } = useKibana(); - const { - config: { documents: cachedDocuments, verbose: cachedVerbose }, - } = testPipelineData; - - const testPipeline = async () => { - const { isValid, data } = await form.submit(); - - if (!isValid) { - return; - } - - const { documents } = data as { documents: Document[] }; - - await handleTestPipeline({ documents: documents!, verbose: cachedVerbose }); - - // This is necessary to update the status and output of each processor - // as verbose may not be enabled - setPerProcessorOutput(documents, processors); - }; - - const { form } = useForm({ - schema: documentsSchema, - defaultValue: { - documents: cachedDocuments || '', - }, - }); - return (
@@ -100,53 +56,46 @@ export const DocumentsTab: React.FunctionComponent = ({ -
- {/* Documents editor */} - + {/* Documents editor */} + - + - - {isRunningTest ? ( - - ) : ( - - )} - - + + {isRunningTest ? ( + + ) : ( + + )} +
); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx index 926bab6da993c..db6a020e307a5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx @@ -18,10 +18,13 @@ import { } from '@elastic/eui'; import { Document } from '../../../types'; -import { HandleTestPipelineArgs } from '../test_pipeline_flyout'; +import { TestPipelineConfig } from '../test_pipeline_flyout.container'; interface Props { - handleTestPipeline: (data: HandleTestPipelineArgs) => void; + handleTestPipeline: ( + testPipelineConfig: TestPipelineConfig, + refreshOutputPerProcessor?: boolean + ) => Promise<{ isSuccessful: boolean }>; isRunningTest: boolean; cachedVerbose?: boolean; cachedDocuments: Document[]; @@ -37,12 +40,6 @@ export const OutputTab: React.FunctionComponent = ({ }) => { const [isVerboseEnabled, setIsVerboseEnabled] = useState(Boolean(cachedVerbose)); - const onEnableVerbose = (isVerbose: boolean) => { - setIsVerboseEnabled(isVerbose); - - handleTestPipeline({ documents: cachedDocuments!, verbose: isVerbose }); - }; - let content: React.ReactNode | undefined; if (isRunningTest) { @@ -78,15 +75,23 @@ export const OutputTab: React.FunctionComponent = ({ /> } checked={isVerboseEnabled} - onChange={(e) => onEnableVerbose(e.target.checked)} data-test-subj="verboseOutputToggle" + onChange={async (e) => { + const isVerbose = e.target.checked; + setIsVerboseEnabled(isVerbose); + + await handleTestPipeline({ documents: cachedDocuments!, verbose: isVerbose }); + }} /> - handleTestPipeline({ documents: cachedDocuments!, verbose: isVerboseEnabled }) + onClick={async () => + await handleTestPipeline( + { documents: cachedDocuments!, verbose: isVerboseEnabled }, + true + ) } iconType="refresh" data-test-subj="refreshOutputButton" diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx index abfb86c2afda1..b13fb2df90984 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx @@ -13,14 +13,9 @@ export type TestPipelineFlyoutTab = 'documents' | 'output'; interface Props { onTabChange: (tab: TestPipelineFlyoutTab) => void; selectedTab: TestPipelineFlyoutTab; - getIsDisabled: (tab: TestPipelineFlyoutTab) => boolean; } -export const Tabs: React.FunctionComponent = ({ - onTabChange, - selectedTab, - getIsDisabled, -}) => { +export const Tabs: React.FunctionComponent = ({ onTabChange, selectedTab }) => { const tabs: Array<{ id: TestPipelineFlyoutTab; name: React.ReactNode; @@ -29,8 +24,8 @@ export const Tabs: React.FunctionComponent = ({ id: 'documents', name: ( ), }, @@ -49,7 +44,6 @@ export const Tabs: React.FunctionComponent = ({ onClick={() => onTabChange(tab.id)} isSelected={tab.id === selectedTab} key={tab.id} - disabled={getIsDisabled(tab.id)} data-test-subj={tab.id.toLowerCase() + 'Tab'} > {tab.name} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx index 8c59d484acd08..6595437c01810 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx @@ -38,7 +38,7 @@ import { OnActionHandler } from '../components/processors_tree'; import { ProcessorRemoveModal, PipelineProcessorsItemTooltip, - ManageProcessorForm, + ProcessorForm, OnSubmitHandler, } from '../components'; @@ -159,12 +159,12 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({ selector: mode.arg.selector, }, }); + break; default: } - setMode({ id: 'idle' }); }, - [processorsDispatch, mode, setMode] + [processorsDispatch, mode] ); const onCloseSettingsForm = useCallback(() => { @@ -208,8 +208,8 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({ }; }, [mode, setMode, processorsState, processorsDispatch]); - // Update the test output whenever the processorsState changes (e.g., on move, update, delete) - // Note: updateTestOutputPerProcessor() will only simulate if the user has added sample documents + // Make a request to the simulate API and update the processor output + // whenever the documents or processorsState changes (e.g., on move, update, delete) useEffect(() => { updateTestOutputPerProcessor(documents, processorsState); }, [documents, processorsState, updateTestOutputPerProcessor]); @@ -233,7 +233,7 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({ )} {mode.id === 'managingProcessor' || mode.id === 'creatingProcessor' ? ( - { + const previousProcessorIndex = processorIndex - count; + + if (previousProcessorIndex >= 0) { + const processorResult = document.processor_results[previousProcessorIndex]; + + if (!processorResult.doc) { + const newCount = count + 1; + return getProcessorInput(processorIndex, document, newCount); + } + + return processorResult.doc; + } + + return undefined; +}; + /** * This function takes the verbose response of the simulate API * and maps the results to each processor in the pipeline by the "tag" field @@ -81,11 +114,9 @@ export const deserializeVerboseTestOutput = ( const result = { ...currentResult }; const resultId = result.tag; + // We skip index 0, as the first processor will not have a previous result if (index !== 0) { - // Add the result from the previous processor so that the user - // can easily compare current output to the previous output - // This may be a result from an on_failure processor - result.prevProcessorResult = doc.processor_results[index - 1]; + result.processorInput = getProcessorInput(index, doc); } // The tag is added programatically as a way to map diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts index 71b2e2fa8f7f1..c462b19c79327 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts @@ -14,9 +14,4 @@ export { OnUpdateHandlerArg, OnUpdateHandler } from './types'; export { SerializeResult } from './serialize'; -export { - LoadFromJsonButton, - OnDoneLoadJsonHandler, - TestPipelineActions, - DocumentsDropdown, -} from './components'; +export { LoadFromJsonButton, OnDoneLoadJsonHandler, TestPipelineActions } from './components'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts index 5229f5eb0bb21..42201b3102c28 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts @@ -94,7 +94,7 @@ export interface ProcessorResult { tag: string; ignored_error?: any; error?: any; - prevProcessorResult?: ProcessorResult; + processorInput?: Document; [key: string]: any; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0977e99fa0c3c..68f6bc166cd1d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9497,9 +9497,6 @@ "xpack.ingestPipelines.requestFlyout.descriptionText": "このElasticsearchリクエストは、このパイプラインを作成または更新します。", "xpack.ingestPipelines.requestFlyout.namedTitle": "「{name}」のリクエスト", "xpack.ingestPipelines.requestFlyout.unnamedTitle": "リクエスト", - "xpack.ingestPipelines.settingsFormOnFailureFlyout.addButtonLabel": "追加", - "xpack.ingestPipelines.settingsFormOnFailureFlyout.cancelButtonLabel": "キャンセル", - "xpack.ingestPipelines.settingsFormOnFailureFlyout.updateButtonLabel": "更新", "xpack.ingestPipelines.tabs.outputTabTitle": "アウトプット", "xpack.ingestPipelines.testPipelineFlyout.documentsForm.documentsFieldLabel": "ドキュメント", "xpack.ingestPipelines.testPipelineFlyout.documentsForm.documentsJsonError": "ドキュメントJSONが無効です。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7f8f2a98abae3..cb43cefdc3655 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9503,9 +9503,6 @@ "xpack.ingestPipelines.requestFlyout.descriptionText": "此 Elasticsearch 请求将创建或更新管道。", "xpack.ingestPipelines.requestFlyout.namedTitle": "对“{name}”的请求", "xpack.ingestPipelines.requestFlyout.unnamedTitle": "请求", - "xpack.ingestPipelines.settingsFormOnFailureFlyout.addButtonLabel": "添加", - "xpack.ingestPipelines.settingsFormOnFailureFlyout.cancelButtonLabel": "取消", - "xpack.ingestPipelines.settingsFormOnFailureFlyout.updateButtonLabel": "更新", "xpack.ingestPipelines.tabs.outputTabTitle": "输出", "xpack.ingestPipelines.testPipelineFlyout.documentsForm.documentsFieldLabel": "文档", "xpack.ingestPipelines.testPipelineFlyout.documentsForm.documentsJsonError": "文档 JSON 无效。", From 6dd558e59cc85ebf3d49c0278c2372aa978f276b Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Tue, 15 Sep 2020 15:30:41 -0500 Subject: [PATCH 02/15] [Security Solution][Detections] Integration test for Editing a Rule (#77090) * Add cypress test around editing a detection rule Right now this just navigates around and verifies that the form is correctly repopulated; next step will be to modify/asset some changes. * Add assertions for editing a rule We already were asserting on the population of the Edit form after creation; this additionally makes modifications, saves them, and asserts the resulting values on the Rule Details page. * Remove unused imports * Inline our cypress expectations So that expectation failures are less obfuscated, the decision was previously made to abstract user navigation into functions, but to leave expectations directly within the test body. * Dynamically assert Rule Details based on titles Rule Details are unfortunately unstructured: they're an array of
s and
s without any hierarchy. To address this, tests were previously hardcoding the order of these fields, and assertions were performed by querying for all
s and then indexing with the hardcoded number (e.g. ABOUT_FALSE_POSITIVES). However, in addition to being unstructured, these fields are also _dynamic_, and will be present/absent depending on the data of the given rule. Thus, we started needing multiple orderings for the different combinations of rule fields/rule types. In the absence of refactoring how we build rule details, I'm introducing a simple helper function to fetch the relevant
by the corresponding
s text. This should be more robust to change and more declarative. * Fix bad merge conflict Lots of these variables no longer exist upstream and this new test needed to be refactored. Co-authored-by: Elastic Machine --- .../alerts_detection_rules_custom.spec.ts | 194 ++++++++++++++---- .../alerts_detection_rules_eql.spec.ts | 64 +++--- .../alerts_detection_rules_ml.spec.ts | 81 ++++---- .../alerts_detection_rules_override.spec.ts | 112 +++++----- .../alerts_detection_rules_threshold.spec.ts | 77 +++---- .../security_solution/cypress/objects/rule.ts | 25 +++ .../cypress/screens/alerts_detection_rules.ts | 2 + .../cypress/screens/create_new_rule.ts | 29 +++ .../cypress/screens/edit_rule.ts | 7 + .../cypress/screens/rule_details.ts | 61 ++---- .../cypress/tasks/alerts_detection_rules.ts | 6 + .../cypress/tasks/create_new_rule.ts | 67 +++--- .../cypress/tasks/edit_rule.ts | 12 ++ .../rules/schedule_item_form/index.tsx | 2 + .../detection_engine/rules/all/columns.tsx | 1 + .../detection_engine/rules/edit/index.tsx | 5 + .../es_archives/custom_rules/data.json.gz | Bin 2885 -> 2975 bytes 17 files changed, 443 insertions(+), 302 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/screens/edit_rule.ts create mode 100644 x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index d9d9fde8fc8cc..17ff1dad79960 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { newRule } from '../objects/rule'; +import { newRule, existingRule } from '../objects/rule'; import { CUSTOM_RULES_BTN, @@ -16,26 +16,16 @@ import { SHOWING_RULES_TEXT, } from '../screens/alerts_detection_rules'; import { - ABOUT_FALSE_POSITIVES, ABOUT_INVESTIGATION_NOTES, - ABOUT_MITRE, - ABOUT_RISK, ABOUT_RULE_DESCRIPTION, - ABOUT_SEVERITY, - ABOUT_STEP, - ABOUT_TAGS, - ABOUT_URLS, - DEFINITION_CUSTOM_QUERY, - DEFINITION_INDEX_PATTERNS, - DEFINITION_TIMELINE, - DEFINITION_STEP, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, - SCHEDULE_LOOPBACK, - SCHEDULE_RUNS, - SCHEDULE_STEP, + getDescriptionForTitle, + ABOUT_DETAILS, + DEFINITION_DETAILS, + SCHEDULE_DETAILS, } from '../screens/rule_details'; import { @@ -53,18 +43,38 @@ import { selectNumberOfRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, + editFirstRule, } from '../tasks/alerts_detection_rules'; import { createAndActivateRule, fillAboutRuleAndContinue, fillDefineCustomRuleWithImportedQueryAndContinue, - expectDefineFormToRepopulateAndContinue, - expectAboutFormToRepopulateAndContinue, + goToAboutStepTab, + goToScheduleStepTab, + goToActionsStepTab, + fillAboutRule, } from '../tasks/create_new_rule'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; +import { + ACTIONS_THROTTLE_INPUT, + CUSTOM_QUERY_INPUT, + DEFINE_INDEX_INPUT, + RULE_NAME_INPUT, + RULE_DESCRIPTION_INPUT, + TAGS_FIELD, + SEVERITY_DROPDOWN, + RISK_INPUT, + SCHEDULE_INTERVAL_AMOUNT_INPUT, + SCHEDULE_INTERVAL_UNITS_INPUT, + DEFINE_EDIT_BUTTON, + DEFINE_CONTINUE_BUTTON, + ABOUT_EDIT_BUTTON, + ABOUT_CONTINUE_BTN, +} from '../screens/create_new_rule'; +import { saveEditedRule } from '../tasks/edit_rule'; describe('Detection rules, custom', () => { before(() => { @@ -84,8 +94,19 @@ describe('Detection rules, custom', () => { goToCreateNewRule(); fillDefineCustomRuleWithImportedQueryAndContinue(newRule); fillAboutRuleAndContinue(newRule); - expectDefineFormToRepopulateAndContinue(newRule); - expectAboutFormToRepopulateAndContinue(newRule); + + // expect define step to repopulate + cy.get(DEFINE_EDIT_BUTTON).click(); + cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', newRule.customQuery); + cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); + cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist'); + + // expect about step to populate + cy.get(ABOUT_EDIT_BUTTON).click(); + cy.get(RULE_NAME_INPUT).invoke('val').should('eq', newRule.name); + cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true }); + cy.get(ABOUT_CONTINUE_BTN).should('not.exist'); + createAndActivateRule(); cy.get(CUSTOM_RULES_BTN).invoke('text').should('eql', 'Custom rules (1)'); @@ -142,32 +163,35 @@ describe('Detection rules, custom', () => { cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${newRule.name} Beta`); cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', newRule.description); - cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', newRule.severity); - cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', newRule.riskScore); - cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls); - cy.get(ABOUT_STEP) - .eq(ABOUT_FALSE_POSITIVES) - .invoke('text') - .should('eql', expectedFalsePositives); - cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre); - cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags); + cy.get(ABOUT_DETAILS).within(() => { + getDescriptionForTitle('Severity').invoke('text').should('eql', newRule.severity); + getDescriptionForTitle('Risk score').invoke('text').should('eql', newRule.riskScore); + getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls); + getDescriptionForTitle('False positive examples') + .invoke('text') + .should('eql', expectedFalsePositives); + getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre); + getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags); + }); cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => { - cy.wrap(patterns).each((pattern, index) => { - cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]); - }); + cy.get(DEFINITION_DETAILS).within(() => { + getDescriptionForTitle('Index patterns') + .invoke('text') + .should('eql', expectedIndexPatterns.join('')); + getDescriptionForTitle('Custom query') + .invoke('text') + .should('eql', `${newRule.customQuery} `); + getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Query'); + getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None'); + }); + + cy.get(SCHEDULE_DETAILS).within(() => { + getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m'); + getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m'); }); - cy.get(DEFINITION_STEP) - .eq(DEFINITION_CUSTOM_QUERY) - .invoke('text') - .should('eql', `${newRule.customQuery} `); - cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None'); - - cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m'); - cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m'); }); }); @@ -233,4 +257,94 @@ describe('Deletes custom rules', () => { .should('eql', `Custom rules (${expectedNumberOfRulesAfterDeletion})`); }); }); + + it('Allows a rule to be edited', () => { + editFirstRule(); + + // expect define step to populate + cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', existingRule.customQuery); + if (existingRule.index && existingRule.index.length > 0) { + cy.get(DEFINE_INDEX_INPUT).invoke('text').should('eq', existingRule.index.join('')); + } + + goToAboutStepTab(); + + // expect about step to populate + cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name); + cy.get(RULE_DESCRIPTION_INPUT).invoke('text').should('eql', existingRule.description); + cy.get(TAGS_FIELD).invoke('text').should('eql', existingRule.tags.join('')); + + cy.get(SEVERITY_DROPDOWN).invoke('text').should('eql', existingRule.severity); + cy.get(RISK_INPUT).invoke('val').should('eql', existingRule.riskScore); + + goToScheduleStepTab(); + + // expect schedule step to populate + const intervalParts = existingRule.interval && existingRule.interval.match(/[0-9]+|[a-zA-Z]+/g); + if (intervalParts) { + const [amount, unit] = intervalParts; + cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', amount); + cy.get(SCHEDULE_INTERVAL_UNITS_INPUT).invoke('val').should('eql', unit); + } else { + throw new Error('Cannot assert scheduling info on a rule without an interval'); + } + + goToActionsStepTab(); + + cy.get(ACTIONS_THROTTLE_INPUT).invoke('val').should('eql', 'no_actions'); + + goToAboutStepTab(); + + const editedRule = { + ...existingRule, + severity: 'Medium', + description: 'Edited Rule description', + }; + + fillAboutRule(editedRule); + saveEditedRule(); + + const expectedTags = editedRule.tags.join(''); + const expectedIndexPatterns = + editedRule.index && editedRule.index.length + ? editedRule.index + : [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ]; + + cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${editedRule.name} Beta`); + + cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', editedRule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDescriptionForTitle('Severity').invoke('text').should('eql', editedRule.severity); + getDescriptionForTitle('Risk score').invoke('text').should('eql', editedRule.riskScore); + getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags); + }); + + cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', editedRule.note); + + cy.get(DEFINITION_DETAILS).within(() => { + getDescriptionForTitle('Index patterns') + .invoke('text') + .should('eql', expectedIndexPatterns.join('')); + getDescriptionForTitle('Custom query') + .invoke('text') + .should('eql', `${editedRule.customQuery} `); + getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Query'); + getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None'); + }); + + if (editedRule.interval) { + cy.get(SCHEDULE_DETAILS).within(() => { + getDescriptionForTitle('Runs every').invoke('text').should('eql', editedRule.interval); + }); + } + }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts index c65cd8406099a..76871929fe050 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts @@ -15,26 +15,16 @@ import { SEVERITY, } from '../screens/alerts_detection_rules'; import { - ABOUT_FALSE_POSITIVES, + ABOUT_DETAILS, ABOUT_INVESTIGATION_NOTES, - ABOUT_MITRE, - ABOUT_RISK, ABOUT_RULE_DESCRIPTION, - ABOUT_SEVERITY, - ABOUT_STEP, - ABOUT_TAGS, - ABOUT_URLS, - DEFINITION_CUSTOM_QUERY, - DEFINITION_INDEX_PATTERNS, - DEFINITION_TIMELINE, - DEFINITION_STEP, + DEFINITION_DETAILS, + getDescriptionForTitle, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, - SCHEDULE_LOOPBACK, - SCHEDULE_RUNS, - SCHEDULE_STEP, + SCHEDULE_DETAILS, } from '../screens/rule_details'; import { @@ -136,32 +126,34 @@ describe('Detection rules, EQL', () => { cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${eqlRule.name} Beta`); cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', eqlRule.description); - cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', eqlRule.severity); - cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', eqlRule.riskScore); - cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls); - cy.get(ABOUT_STEP) - .eq(ABOUT_FALSE_POSITIVES) - .invoke('text') - .should('eql', expectedFalsePositives); - cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre); - cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags); + cy.get(ABOUT_DETAILS).within(() => { + getDescriptionForTitle('Severity').invoke('text').should('eql', eqlRule.severity); + getDescriptionForTitle('Risk score').invoke('text').should('eql', eqlRule.riskScore); + getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls); + getDescriptionForTitle('False positive examples') + .invoke('text') + .should('eql', expectedFalsePositives); + getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre); + getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags); + }); cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => { - cy.wrap(patterns).each((pattern, index) => { - cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]); - }); + cy.get(DEFINITION_DETAILS).within(() => { + getDescriptionForTitle('Index patterns') + .invoke('text') + .should('eql', expectedIndexPatterns.join('')); + getDescriptionForTitle('Custom query') + .invoke('text') + .should('eql', `${eqlRule.customQuery} `); + getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Event Correlation'); + getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None'); + }); + + cy.get(SCHEDULE_DETAILS).within(() => { + getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m'); + getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m'); }); - cy.get(DEFINITION_STEP) - .eq(DEFINITION_CUSTOM_QUERY) - .invoke('text') - .should('eql', `${eqlRule.customQuery} `); - cy.get(DEFINITION_STEP).eq(2).invoke('text').should('eql', 'Event Correlation'); - cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None'); - - cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m'); - cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m'); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts index b6b30ef550eb1..47e49d48e2aec 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts @@ -16,24 +16,14 @@ import { SEVERITY, } from '../screens/alerts_detection_rules'; import { - ABOUT_FALSE_POSITIVES, - ABOUT_MITRE, - ABOUT_RISK, ABOUT_RULE_DESCRIPTION, - ABOUT_SEVERITY, - ABOUT_STEP, - ABOUT_TAGS, - ABOUT_URLS, - ANOMALY_SCORE, - DEFINITION_TIMELINE, - DEFINITION_STEP, MACHINE_LEARNING_JOB_ID, MACHINE_LEARNING_JOB_STATUS, RULE_NAME_HEADER, - SCHEDULE_LOOPBACK, - SCHEDULE_RUNS, - SCHEDULE_STEP, - RULE_TYPE, + getDescriptionForTitle, + ABOUT_DETAILS, + DEFINITION_DETAILS, + SCHEDULE_DETAILS, } from '../screens/rule_details'; import { @@ -126,36 +116,37 @@ describe('Detection rules, machine learning', () => { cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${machineLearningRule.name} Beta`); cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', machineLearningRule.description); - cy.get(ABOUT_STEP) - .eq(ABOUT_SEVERITY) - .invoke('text') - .should('eql', machineLearningRule.severity); - cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', machineLearningRule.riskScore); - cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls); - cy.get(ABOUT_STEP) - .eq(ABOUT_FALSE_POSITIVES) - .invoke('text') - .should('eql', expectedFalsePositives); - cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre); - cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags); - - cy.get(DEFINITION_STEP).eq(RULE_TYPE).invoke('text').should('eql', 'Machine Learning'); - cy.get(DEFINITION_STEP) - .eq(ANOMALY_SCORE) - .invoke('text') - .should('eql', machineLearningRule.anomalyScoreThreshold); - cy.get(DEFINITION_STEP) - .get(MACHINE_LEARNING_JOB_STATUS) - .invoke('text') - .should('eql', 'Stopped'); - cy.get(DEFINITION_STEP) - .get(MACHINE_LEARNING_JOB_ID) - .invoke('text') - .should('eql', machineLearningRule.machineLearningJob); - - cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None'); - - cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m'); - cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m'); + cy.get(ABOUT_DETAILS).within(() => { + getDescriptionForTitle('Severity').invoke('text').should('eql', machineLearningRule.severity); + getDescriptionForTitle('Risk score') + .invoke('text') + .should('eql', machineLearningRule.riskScore); + getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls); + getDescriptionForTitle('False positive examples') + .invoke('text') + .should('eql', expectedFalsePositives); + getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre); + getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags); + }); + + cy.get(DEFINITION_DETAILS).within(() => { + getDescriptionForTitle('Anomaly score') + .invoke('text') + .should('eql', machineLearningRule.anomalyScoreThreshold); + getDescriptionForTitle('Anomaly score') + .invoke('text') + .should('eql', machineLearningRule.anomalyScoreThreshold); + getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Machine Learning'); + getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None'); + cy.get(MACHINE_LEARNING_JOB_STATUS).invoke('text').should('eql', 'Stopped'); + cy.get(MACHINE_LEARNING_JOB_ID) + .invoke('text') + .should('eql', machineLearningRule.machineLearningJob); + }); + + cy.get(SCHEDULE_DETAILS).within(() => { + getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m'); + getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m'); + }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts index e3526c63e2310..4edf5e1866087 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts @@ -16,29 +16,17 @@ import { } from '../screens/alerts_detection_rules'; import { ABOUT_INVESTIGATION_NOTES, - ABOUT_OVERRIDE_FALSE_POSITIVES, - ABOUT_OVERRIDE_MITRE, - ABOUT_OVERRIDE_NAME_OVERRIDE, - ABOUT_OVERRIDE_RISK, - ABOUT_OVERRIDE_RISK_OVERRIDE, - ABOUT_OVERRIDE_SEVERITY_OVERRIDE, - ABOUT_OVERRIDE_TAGS, - ABOUT_OVERRIDE_TIMESTAMP_OVERRIDE, - ABOUT_OVERRIDE_URLS, ABOUT_RULE_DESCRIPTION, - ABOUT_SEVERITY, - ABOUT_STEP, - DEFINITION_CUSTOM_QUERY, - DEFINITION_INDEX_PATTERNS, - DEFINITION_TIMELINE, - DEFINITION_STEP, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, - SCHEDULE_LOOPBACK, - SCHEDULE_RUNS, - SCHEDULE_STEP, + ABOUT_DETAILS, + getDescriptionForTitle, + DEFINITION_DETAILS, + SCHEDULE_DETAILS, + DETAILS_TITLE, + DETAILS_DESCRIPTION, } from '../screens/rule_details'; import { @@ -141,56 +129,56 @@ describe('Detection rules, override', () => { const expectedOverrideSeverities = ['Low', 'Medium', 'High', 'Critical']; - cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', newOverrideRule.severity); - newOverrideRule.severityOverride.forEach((severity, i) => { - cy.get(ABOUT_STEP) - .eq(ABOUT_OVERRIDE_SEVERITY_OVERRIDE + i) + cy.get(ABOUT_DETAILS).within(() => { + getDescriptionForTitle('Severity').invoke('text').should('eql', newOverrideRule.severity); + getDescriptionForTitle('Risk score').invoke('text').should('eql', newOverrideRule.riskScore); + getDescriptionForTitle('Risk score override') .invoke('text') - .should( - 'eql', - `${severity.sourceField}:${severity.sourceValue}${expectedOverrideSeverities[i]}` - ); + .should('eql', `${newOverrideRule.riskOverride}signal.rule.risk_score`); + getDescriptionForTitle('Rule name override') + .invoke('text') + .should('eql', newOverrideRule.nameOverride); + getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls); + getDescriptionForTitle('False positive examples') + .invoke('text') + .should('eql', expectedFalsePositives); + getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre); + getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags); + getDescriptionForTitle('Timestamp override') + .invoke('text') + .should('eql', newOverrideRule.timestampOverride); + cy.contains(DETAILS_TITLE, 'Severity override') + .invoke('index', DETAILS_TITLE) // get index relative to other titles, not all siblings + .then((severityOverrideIndex) => { + newOverrideRule.severityOverride.forEach((severity, i) => { + cy.get(DETAILS_DESCRIPTION) + .eq(severityOverrideIndex + i) + .invoke('text') + .should( + 'eql', + `${severity.sourceField}:${severity.sourceValue}${expectedOverrideSeverities[i]}` + ); + }); + }); }); - cy.get(ABOUT_STEP) - .eq(ABOUT_OVERRIDE_RISK) - .invoke('text') - .should('eql', newOverrideRule.riskScore); - cy.get(ABOUT_STEP) - .eq(ABOUT_OVERRIDE_RISK_OVERRIDE) - .invoke('text') - .should('eql', `${newOverrideRule.riskOverride}signal.rule.risk_score`); - cy.get(ABOUT_STEP).eq(ABOUT_OVERRIDE_URLS).invoke('text').should('eql', expectedUrls); - cy.get(ABOUT_STEP) - .eq(ABOUT_OVERRIDE_FALSE_POSITIVES) - .invoke('text') - .should('eql', expectedFalsePositives); - cy.get(ABOUT_STEP) - .eq(ABOUT_OVERRIDE_NAME_OVERRIDE) - .invoke('text') - .should('eql', newOverrideRule.nameOverride); - cy.get(ABOUT_STEP).eq(ABOUT_OVERRIDE_MITRE).invoke('text').should('eql', expectedMitre); - cy.get(ABOUT_STEP) - .eq(ABOUT_OVERRIDE_TIMESTAMP_OVERRIDE) - .invoke('text') - .should('eql', newOverrideRule.timestampOverride); - cy.get(ABOUT_STEP).eq(ABOUT_OVERRIDE_TAGS).invoke('text').should('eql', expectedTags); - cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => { - cy.wrap(patterns).each((pattern, index) => { - cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]); - }); + cy.get(DEFINITION_DETAILS).within(() => { + getDescriptionForTitle('Index patterns') + .invoke('text') + .should('eql', expectedIndexPatterns.join('')); + getDescriptionForTitle('Custom query') + .invoke('text') + .should('eql', `${newOverrideRule.customQuery} `); + getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Query'); + getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None'); + }); + + cy.get(SCHEDULE_DETAILS).within(() => { + getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m'); + getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m'); }); - cy.get(DEFINITION_STEP) - .eq(DEFINITION_CUSTOM_QUERY) - .invoke('text') - .should('eql', `${newOverrideRule.customQuery} `); - cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None'); - - cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m'); - cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m'); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts index 10f9ebb5623df..00175ed3baeb8 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts @@ -15,27 +15,16 @@ import { SEVERITY, } from '../screens/alerts_detection_rules'; import { - ABOUT_FALSE_POSITIVES, ABOUT_INVESTIGATION_NOTES, - ABOUT_MITRE, - ABOUT_RISK, ABOUT_RULE_DESCRIPTION, - ABOUT_SEVERITY, - ABOUT_STEP, - ABOUT_TAGS, - ABOUT_URLS, - DEFINITION_CUSTOM_QUERY, - DEFINITION_INDEX_PATTERNS, - DEFINITION_THRESHOLD, - DEFINITION_TIMELINE, - DEFINITION_STEP, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, - SCHEDULE_LOOPBACK, - SCHEDULE_RUNS, - SCHEDULE_STEP, + getDescriptionForTitle, + ABOUT_DETAILS, + DEFINITION_DETAILS, + SCHEDULE_DETAILS, } from '../screens/rule_details'; import { @@ -137,38 +126,40 @@ describe('Detection rules, threshold', () => { cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${newThresholdRule.name} Beta`); cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', newThresholdRule.description); - cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', newThresholdRule.severity); - cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', newThresholdRule.riskScore); - cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls); - cy.get(ABOUT_STEP) - .eq(ABOUT_FALSE_POSITIVES) - .invoke('text') - .should('eql', expectedFalsePositives); - cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre); - cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags); + cy.get(ABOUT_DETAILS).within(() => { + getDescriptionForTitle('Severity').invoke('text').should('eql', newThresholdRule.severity); + getDescriptionForTitle('Risk score').invoke('text').should('eql', newThresholdRule.riskScore); + getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls); + getDescriptionForTitle('False positive examples') + .invoke('text') + .should('eql', expectedFalsePositives); + getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre); + getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags); + }); cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => { - cy.wrap(patterns).each((pattern, index) => { - cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]); - }); + cy.get(DEFINITION_DETAILS).within(() => { + getDescriptionForTitle('Index patterns') + .invoke('text') + .should('eql', expectedIndexPatterns.join('')); + getDescriptionForTitle('Custom query') + .invoke('text') + .should('eql', `${newThresholdRule.customQuery} `); + getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Threshold'); + getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None'); + getDescriptionForTitle('Threshold') + .invoke('text') + .should( + 'eql', + `Results aggregated by ${newThresholdRule.thresholdField} >= ${newThresholdRule.threshold}` + ); + }); + + cy.get(SCHEDULE_DETAILS).within(() => { + getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m'); + getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m'); }); - cy.get(DEFINITION_STEP) - .eq(DEFINITION_CUSTOM_QUERY) - .invoke('text') - .should('eql', `${newThresholdRule.customQuery} `); - cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None'); - cy.get(DEFINITION_STEP) - .eq(DEFINITION_THRESHOLD) - .invoke('text') - .should( - 'eql', - `Results aggregated by ${newThresholdRule.thresholdField} >= ${newThresholdRule.threshold}` - ); - - cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m'); - cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m'); }); }); diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 0624606fe8481..2a5c60815f450 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -27,6 +27,8 @@ export interface CustomRule { customQuery: string; name: string; description: string; + index?: string[]; + interval?: string; severity: string; riskScore: string; tags: string[]; @@ -109,6 +111,29 @@ export const newRule: CustomRule = { timelineId: '0162c130-78be-11ea-9718-118a926974a4', }; +export const existingRule: CustomRule = { + customQuery: 'host.name:*', + name: 'Rule 1', + description: 'Description for Rule 1', + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + interval: '4m', + severity: 'High', + riskScore: '19', + tags: ['rule1'], + referenceUrls: [], + falsePositivesExamples: [], + mitre: [], + note: 'This is my note', + timelineId: '', +}; + export const newOverrideRule: OverrideRule = { customQuery: 'host.name:*', name: 'New Rule Test', diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index a41b8296f83e4..14f5383939a94 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -14,6 +14,8 @@ export const CUSTOM_RULES_BTN = '[data-test-subj="show-custom-rules-filter-butto export const DELETE_RULE_ACTION_BTN = '[data-test-subj="deleteRuleAction"]'; +export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]'; + export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]'; export const ELASTIC_RULES_BTN = '[data-test-subj="show-elastic-rules-filter-button"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index 1c25ed88c3bee..dda371126d5aa 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -8,6 +8,13 @@ export const ABOUT_CONTINUE_BTN = '[data-test-subj="about-continue"]'; export const ABOUT_EDIT_BUTTON = '[data-test-subj="edit-about-rule"]'; +export const ABOUT_EDIT_TAB = '[data-test-subj="edit-rule-about-tab"]'; + +export const ACTIONS_EDIT_TAB = '[data-test-subj="edit-rule-actions-tab"]'; + +export const ACTIONS_THROTTLE_INPUT = + '[data-test-subj="stepRuleActions"] [data-test-subj="select"]'; + export const ADD_FALSE_POSITIVE_BTN = '[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text'; @@ -30,6 +37,11 @@ export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]'; export const DEFINE_EDIT_BUTTON = '[data-test-subj="edit-define-rule"]'; +export const DEFINE_EDIT_TAB = '[data-test-subj="edit-rule-define-tab"]'; + +export const DEFINE_INDEX_INPUT = + '[data-test-subj="detectionEngineStepDefineRuleIndices"] [data-test-subj="input"]'; + export const EQL_TYPE = '[data-test-subj="eqlRuleType"]'; export const EQL_QUERY_INPUT = '[data-test-subj="eqlQueryBarTextInput"]'; @@ -81,6 +93,20 @@ export const RULE_TIMESTAMP_OVERRIDE = export const SCHEDULE_CONTINUE_BUTTON = '[data-test-subj="schedule-continue"]'; +export const SCHEDULE_EDIT_TAB = '[data-test-subj="edit-rule-schedule-tab"]'; + +export const SCHEDULE_INTERVAL_AMOUNT_INPUT = + '[data-test-subj="detectionEngineStepScheduleRuleInterval"] [data-test-subj="schedule-amount-input"]'; + +export const SCHEDULE_INTERVAL_UNITS_INPUT = + '[data-test-subj="detectionEngineStepScheduleRuleInterval"] [data-test-subj="schedule-units-input"]'; + +export const SCHEDULE_LOOKBACK_AMOUNT_INPUT = + '[data-test-subj="detectionEngineStepScheduleRuleFrom"] [data-test-subj="schedule-amount-input"]'; + +export const SCHEDULE_LOOKBACK_UNITS_INPUT = + '[data-test-subj="detectionEngineStepScheduleRuleFrom"] [data-test-subj="schedule-units-input"]'; + export const SEVERITY_DROPDOWN = '[data-test-subj="detectionEngineStepAboutRuleSeverity"] [data-test-subj="select"]'; @@ -88,6 +114,9 @@ export const SEVERITY_MAPPING_OVERRIDE_OPTION = '#severity-mapping-override'; export const SEVERITY_OVERRIDE_ROW = '[data-test-subj="severityOverrideRow"]'; +export const TAGS_FIELD = + '[data-test-subj="detectionEngineStepAboutRuleTags"] [data-test-subj="comboBoxInput"]'; + export const TAGS_INPUT = '[data-test-subj="detectionEngineStepAboutRuleTags"] [data-test-subj="comboBoxSearchInput"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts b/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts new file mode 100644 index 0000000000000..1bf0ff34ebd94 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const EDIT_SUBMIT_BUTTON = '[data-test-subj="ruleEditSubmitButton"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index b221709966943..98fc7b06a9908 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -4,55 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -export const ABOUT_FALSE_POSITIVES = 3; +export const getDescriptionForTitle = (title: string) => + cy.get(DETAILS_TITLE).contains(title).next(DETAILS_DESCRIPTION); -export const ABOUT_INVESTIGATION_NOTES = '[data-test-subj="stepAboutDetailsNoteContent"]'; - -export const ABOUT_MITRE = 4; - -export const ABOUT_OVERRIDE_FALSE_POSITIVES = 8; - -export const ABOUT_OVERRIDE_MITRE = 10; - -export const ABOUT_OVERRIDE_NAME_OVERRIDE = 9; - -export const ABOUT_OVERRIDE_RISK = 5; +export const DETAILS_DESCRIPTION = '.euiDescriptionList__description'; +export const DETAILS_TITLE = '.euiDescriptionList__title'; -export const ABOUT_OVERRIDE_RISK_OVERRIDE = 6; - -export const ABOUT_OVERRIDE_SEVERITY_OVERRIDE = 1; - -export const ABOUT_OVERRIDE_TAGS = 12; - -export const ABOUT_OVERRIDE_TIMESTAMP_OVERRIDE = 11; - -export const ABOUT_OVERRIDE_URLS = 7; +export const ABOUT_INVESTIGATION_NOTES = '[data-test-subj="stepAboutDetailsNoteContent"]'; export const ABOUT_RULE_DESCRIPTION = '[data-test-subj=stepAboutRuleDetailsToggleDescriptionText]'; -export const ABOUT_RISK = 1; +export const ABOUT_DETAILS = + '[data-test-subj="aboutRule"] [data-test-subj="listItemColumnStepRuleDescription"]'; -export const ABOUT_SEVERITY = 0; - -export const ABOUT_STEP = '[data-test-subj="aboutRule"] .euiDescriptionList__description'; - -export const ABOUT_TAGS = 5; - -export const ABOUT_URLS = 2; - -export const ANOMALY_SCORE = 1; - -export const DEFINITION_CUSTOM_QUERY = 1; - -export const DEFINITION_THRESHOLD = 4; - -export const DEFINITION_TIMELINE = 3; - -export const DEFINITION_INDEX_PATTERNS = - '[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"] .euiDescriptionList__description .euiBadge__text'; - -export const DEFINITION_STEP = - '[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"] .euiDescriptionList__description'; +export const DEFINITION_DETAILS = + '[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"]'; export const INVESTIGATION_NOTES_MARKDOWN = 'test markdown'; @@ -60,16 +26,13 @@ export const INVESTIGATION_NOTES_TOGGLE = 1; export const MACHINE_LEARNING_JOB_ID = '[data-test-subj="machineLearningJobId"]'; -export const MACHINE_LEARNING_JOB_STATUS = '[data-test-subj="machineLearningJobStatus" ]'; +export const MACHINE_LEARNING_JOB_STATUS = '[data-test-subj="machineLearningJobStatus"]'; export const RULE_ABOUT_DETAILS_HEADER_TOGGLE = '[data-test-subj="stepAboutDetailsToggle"]'; export const RULE_NAME_HEADER = '[data-test-subj="header-page-title"]'; -export const RULE_TYPE = 0; +export const SCHEDULE_DETAILS = + '[data-test-subj=schedule] [data-test-subj="listItemColumnStepRuleDescription"]'; export const SCHEDULE_STEP = '[data-test-subj="schedule"] .euiDescriptionList__description'; - -export const SCHEDULE_RUNS = 0; - -export const SCHEDULE_LOOPBACK = 1; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 5ec5bb97250db..c530594508f95 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -24,6 +24,7 @@ import { SORT_RULES_BTN, THREE_HUNDRED_ROWS, EXPORT_ACTION_BTN, + EDIT_RULE_ACTION_BTN, } from '../screens/alerts_detection_rules'; export const activateRule = (rulePosition: number) => { @@ -35,6 +36,11 @@ export const changeToThreeHundredRowsPerPage = () => { cy.get(THREE_HUNDRED_ROWS).click(); }; +export const editFirstRule = () => { + cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true }); + cy.get(EDIT_RULE_ACTION_BTN).click(); +}; + export const deleteFirstRule = () => { cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true }); cy.get(DELETE_RULE_ACTION_BTN).click(); diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index f26a77171462c..0daff52de7063 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -13,14 +13,17 @@ import { } from '../objects/rule'; import { ABOUT_CONTINUE_BTN, - ANOMALY_THRESHOLD_INPUT, + ABOUT_EDIT_TAB, + ACTIONS_EDIT_TAB, ADD_FALSE_POSITIVE_BTN, ADD_REFERENCE_URL_BTN, ADVANCED_SETTINGS_BTN, + ANOMALY_THRESHOLD_INPUT, COMBO_BOX_INPUT, CREATE_AND_ACTIVATE_BTN, CUSTOM_QUERY_INPUT, DEFINE_CONTINUE_BUTTON, + DEFINE_EDIT_TAB, FALSE_POSITIVES_INPUT, IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK, INPUT, @@ -32,8 +35,8 @@ import { MITRE_TACTIC, MITRE_TACTIC_DROPDOWN, MITRE_TECHNIQUES_INPUT, - RISK_INPUT, REFERENCE_URLS_INPUT, + RISK_INPUT, RISK_MAPPING_OVERRIDE_OPTION, RISK_OVERRIDE, RULE_DESCRIPTION_INPUT, @@ -41,6 +44,7 @@ import { RULE_NAME_OVERRIDE, RULE_TIMESTAMP_OVERRIDE, SCHEDULE_CONTINUE_BUTTON, + SCHEDULE_EDIT_TAB, SEVERITY_DROPDOWN, SEVERITY_MAPPING_OVERRIDE_OPTION, SEVERITY_OVERRIDE_ROW, @@ -48,8 +52,6 @@ import { THRESHOLD_FIELD_SELECTION, THRESHOLD_INPUT_AREA, THRESHOLD_TYPE, - DEFINE_EDIT_BUTTON, - ABOUT_EDIT_BUTTON, EQL_TYPE, EQL_QUERY_INPUT, } from '../screens/create_new_rule'; @@ -61,11 +63,9 @@ export const createAndActivateRule = () => { cy.get(CREATE_AND_ACTIVATE_BTN).should('not.exist'); }; -export const fillAboutRuleAndContinue = ( - rule: CustomRule | MachineLearningRule | ThresholdRule -) => { - cy.get(RULE_NAME_INPUT).type(rule.name, { force: true }); - cy.get(RULE_DESCRIPTION_INPUT).type(rule.description, { force: true }); +export const fillAboutRule = (rule: CustomRule | MachineLearningRule | ThresholdRule) => { + cy.get(RULE_NAME_INPUT).clear({ force: true }).type(rule.name, { force: true }); + cy.get(RULE_DESCRIPTION_INPUT).clear({ force: true }).type(rule.description, { force: true }); cy.get(SEVERITY_DROPDOWN).click({ force: true }); cy.get(`#${rule.severity.toLowerCase()}`).click(); @@ -79,12 +79,15 @@ export const fillAboutRuleAndContinue = ( cy.get(ADVANCED_SETTINGS_BTN).click({ force: true }); rule.referenceUrls.forEach((url, index) => { - cy.get(REFERENCE_URLS_INPUT).eq(index).type(url, { force: true }); + cy.get(REFERENCE_URLS_INPUT).eq(index).clear({ force: true }).type(url, { force: true }); cy.get(ADD_REFERENCE_URL_BTN).click({ force: true }); }); rule.falsePositivesExamples.forEach((falsePositive, index) => { - cy.get(FALSE_POSITIVES_INPUT).eq(index).type(falsePositive, { force: true }); + cy.get(FALSE_POSITIVES_INPUT) + .eq(index) + .clear({ force: true }) + .type(falsePositive, { force: true }); cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true }); }); @@ -93,14 +96,22 @@ export const fillAboutRuleAndContinue = ( cy.contains(MITRE_TACTIC, mitre.tactic).click(); mitre.techniques.forEach((technique) => { - cy.get(MITRE_TECHNIQUES_INPUT).eq(index).type(`${technique}{enter}`, { force: true }); + cy.get(MITRE_TECHNIQUES_INPUT) + .eq(index) + .clear({ force: true }) + .type(`${technique}{enter}`, { force: true }); }); cy.get(MITRE_BTN).click({ force: true }); }); - cy.get(INVESTIGATION_NOTES_TEXTAREA).type(rule.note, { force: true }); + cy.get(INVESTIGATION_NOTES_TEXTAREA).clear({ force: true }).type(rule.note, { force: true }); +}; +export const fillAboutRuleAndContinue = ( + rule: CustomRule | MachineLearningRule | ThresholdRule +) => { + fillAboutRule(rule); cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true }); }; @@ -179,20 +190,6 @@ export const fillDefineCustomRuleWithImportedQueryAndContinue = ( cy.get(CUSTOM_QUERY_INPUT).should('not.exist'); }; -export const expectDefineFormToRepopulateAndContinue = (rule: CustomRule) => { - cy.get(DEFINE_EDIT_BUTTON).click(); - cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', rule.customQuery); - cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); - cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist'); -}; - -export const expectAboutFormToRepopulateAndContinue = (rule: CustomRule) => { - cy.get(ABOUT_EDIT_BUTTON).click(); - cy.get(RULE_NAME_INPUT).invoke('val').should('eq', rule.name); - cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true }); - cy.get(ABOUT_CONTINUE_BTN).should('not.exist'); -}; - export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => { const thresholdField = 0; const threshold = 1; @@ -230,6 +227,22 @@ export const fillDefineMachineLearningRuleAndContinue = (rule: MachineLearningRu cy.get(MACHINE_LEARNING_DROPDOWN).should('not.exist'); }; +export const goToDefineStepTab = () => { + cy.get(DEFINE_EDIT_TAB).click({ force: true }); +}; + +export const goToAboutStepTab = () => { + cy.get(ABOUT_EDIT_TAB).click({ force: true }); +}; + +export const goToScheduleStepTab = () => { + cy.get(SCHEDULE_EDIT_TAB).click({ force: true }); +}; + +export const goToActionsStepTab = () => { + cy.get(ACTIONS_EDIT_TAB).click({ force: true }); +}; + export const selectMachineLearningRuleType = () => { cy.get(MACHINE_LEARNING_TYPE).click({ force: true }); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts new file mode 100644 index 0000000000000..690a36058ec33 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts @@ -0,0 +1,12 @@ +/* + * 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 { EDIT_SUBMIT_BUTTON } from '../screens/edit_rule'; + +export const saveEditedRule = () => { + cy.get(EDIT_SUBMIT_BUTTON).should('exist').click({ force: true }); + cy.get(EDIT_SUBMIT_BUTTON).should('not.exist'); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.tsx index bb33767f4f5d5..867be1c1270e8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.tsx @@ -145,6 +145,7 @@ export const ScheduleItem = ({ void ) => [ { + 'data-test-subj': 'editRuleAction', description: i18n.EDIT_RULE_SETTINGS, icon: 'controlsHorizontal', name: i18n.EDIT_RULE_SETTINGS, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index 5f4fd59669103..e2772af72da06 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -118,6 +118,7 @@ const EditRulePageComponent: FC = () => { const tabs = useMemo( () => [ { + 'data-test-subj': 'edit-rule-define-tab', id: RuleStep.defineRule, name: ruleI18n.DEFINITION, disabled: rule?.immutable, @@ -140,6 +141,7 @@ const EditRulePageComponent: FC = () => { ), }, { + 'data-test-subj': 'edit-rule-about-tab', id: RuleStep.aboutRule, name: ruleI18n.ABOUT, disabled: rule?.immutable, @@ -163,6 +165,7 @@ const EditRulePageComponent: FC = () => { ), }, { + 'data-test-subj': 'edit-rule-schedule-tab', id: RuleStep.scheduleRule, name: ruleI18n.SCHEDULE, disabled: rule?.immutable, @@ -185,6 +188,7 @@ const EditRulePageComponent: FC = () => { ), }, { + 'data-test-subj': 'edit-rule-actions-tab', id: RuleStep.ruleActions, name: ruleI18n.ACTIONS, content: ( @@ -387,6 +391,7 @@ const EditRulePageComponent: FC = () => { QOZ*BnXTWNFZHW&VWe+6$owN33eK|XhY zJ^kRgT(z@ zOy+S{c4TSDMK|ECCh#I(e~^${kDe-si}cX)+<+GTeqZ>zjfcT=l=uKq1KkJXD^X9T+%_`U3`4KLSkNfhWX7L5#x^fLIbh$i( zSLf1ULk&(w^=ncOFN~qvb>XlvTU=aUU0MMcct+b*Oli?1z9F>dliB4_wdMB?-RkAV zkvH>-Q*c$COR(ma#p;FW*rUP(Ra|mp4~r7qy|oSNrkR+QB19UTgbM}!dF31YA9J=&aUE4}YktjWS zg15u`!h}9@gB4GH$qX8|h_DrE6eeb?in!RMO5CoXiy^zajFCPfj|?}nT-!6(b&o2+ zMy?qlGl6at5r2`@f`jIrHF@(nz;W415~2-QQ)&gy=8VFj?}cHaQL6NtJ5Q2o^jBtl z*9()j-Juoa=*L;)lA=r_FAB#|cz*BHViGZHhr?iEpvVrhJtoOAY1^R}glufL`K<*yOo?xWtJz!TFx!#pm!kdTltarQw&jxD!{60*trbe!4ZJmBHUzJ? zs>!wi(fI}|ii7*&6v_vIJ|sA@$)>KpRuA{IV53X0z{`waG|Ml(LpZfayY8tGH@50KcN`8$ZoP_J z((8X|W??Hxf{jz^k|LixA2^iO3fY?QomN*fd#m9ovEhAy#i<-816AckC__$F!5gjX zc|KAy3Q`_Otg6B{@Xh>&kQz7vsq2Nv-c=cpFtl8AD`XFb-i(6Qvw{fO z)-rKHi8k&+!Iucnf_frk&bm#Yi;s0~zGy<@5v>?~$u#@M z?4EkQUvEmq_eB|+>uaOhc7o4f0s#wn^$nOHsz*ytEb}G`C>A%V!x*yjM3I3ZA+Se$ z__9RF({eYw;CQ@>R)-YK^qxtn9{a|I+o30UlF**zpePFIKQX;`Mvg}OhEb%#N68Uw zgLw4Z?u?&T^=rb$XIA_Y$c4Ai@HxmazzXJ!LWi1-uw7;ii;qn z|K0;}G0#I*k>r0H@r`-B3i0Gp!w>)zB|0`hoWwwEsEnc;kUnC-Q~l{04?pXr#%;A! zwSv;{qSLdWqXc24doj7bJtMb+u2#0qnRzALO7qEK&zPfAL%AHDbmp>;>)hFyI5fOP z!|F{21FteO&YLwuRB^R2)l6GFop^&YFE|bh;^jiEAIZg|u;rKOuwkj$yjmFEtc^jT zcYRssJCbOSuDdh~ZS8n)>OdeA%csK05ig0u(!`rcUMaX-I6^II8s%lZQ}Ao;?s5zi zl}^l?G|hffcG~TK>QaM#c$Jk^{&QYx@Grj9*y@)1=u3=uy2eOdQamd8JGeR6%3r_X zvVhujh%9RJtWOU&)DP$KxnFW`zBdrFig~yEdT_yj00D4B zM_N|;p7#d!R`&h}y*2>caPNBqPSgwp#UzG6ue>+daY^uU5WoLR0{Rg+R^X*K1ON#y zF$qaRbcVPlH7_RCDnp%i7zKGw76Ih&K7hSvF67yXZNwF^+Ey0!f@mh`szvswm~4Gr zO0QD1%J@INRhdaPb;AZ?)K8IRM?Oh5+{deHBnVc{d`FuL&l?qxJ}RLdZZ&DqiFB&6 z;E`Qj5Lf|%%&wm5^dR~mh)W12>6%~1j z`z`L=Uran}6nhgV7WJ{j!J?q57{2>y>BETgg5Ng%Sj6GgJOC`*_>Iqp5C^o zk_?;kctetbAXTEl3Zn}G$3THl$D#tr5JiWgfFS^q@L7`#C%)6+7oBRW&iDD!>8Lj; zjc)_(n)J2hq=p7PrG3=AnqjwmDs{a{K`<{G9^n=(F)*9u#;`I}PtN)kQ|!pSqNI@H zX^$({@ytWlV+Yx?!S%0n(!HGa&66PD4vW=}5wxLGghs`gmu6F?fosFgNUD_lS>!dP z7PipQKpc*WT_EGyVyGVa$CYK-U5xqpRampd`gnOXy_9+N+zhVz;t+|I0;iuW-DviY zBpIYU2lG&2RppcZa+1&eUW!%>A%A=&9MXNi5)L1lZ~&a4D5^ovss^NSmZ5O;tf~`+ z23Qh3YiZO$Y*^M;!r}D^2Swxz0F^kNkhN7jfkX%xq6!=jp&&w-iE8&uIJ_Lh?>*tL z^5`ndi*GqMt%3nj5e$k7)tNys1Bs|G2uhfdGz5si8HNtj^9PGg+9g2Z{n94|aa(w1YW9}*ys+OBR4f{tr1N4>gKQ;uhi`s@lg2n19@Z36tz4m~1Y3r$K>xNg7inzX=Yjlld0 z29oz5-X08my+U!fnE&!C6z>_AyysgJj|L<3u+8(T1Q=crHF_-5Wd;!h8A;(WJ#Qmf zSER3CGs`AlkT_Ca|c;~!sX8K#?dDpz|ohX5O%j*sMF=u`C`+YFy1u4rf=DdzM VkQLM)fjOId&foFMcHvn-004vgz_(-RVC{TO3U06) zCnf*z2ma`gq!zn0jPl4JreYE(n1m<-(k1?lN~&O*8o)YK2qk7ZNYc+IbeeP(hmMJT zdxuR7uRw} zjw@AEKJ4zgv-VjpY^q+B&ByKLr1|Hc_g8s3@oAVWmAaTkj+Jd;ab3pVqB_ylN?k~C zM9F@Fz>WkYAOKmA)D8rBVB~=&Dynh0p+jM|MHuz5>Cyz3D2!>Q!iyui@4CFg<~Xg_ zkYk@k*ta;O+i~}?mzrxUc4-b~rH*ijJ+@*+yp&L8h0Z83rj6ZrlXNbPfH{$KLsuoQ z$WEF%ZbZXJ+Ju3Zi1LGpAgh3_3UfTZ)I>e^m5?D#cJvfqPxwQKLhQ2zFaB5=G-(kN zCo(xK!o4c$lRj74{R+Mqy1B~;TSK~KxQXMtL4R4dRS7Qk`!VjPPz_@m&Q`VH;c0tG z)_e{Euv$qYyar=NtWIWmpDt&HynQPS3rU3aBCh@(*)9pCw_ zgeSt%rwD}FICi7eWsK96@wS72MPg!_`Bi=Ggw&=Xi7*@DFI%LZI`m^GjzcImnUaD2{K zzNaO_D&Ee6eMtwi;X>I2nNYzR5L6o(0)!L`x}quk8zdSpM+Vfg-Y3HSb%;~w)_l#? zRXQJTQdddsl|%B+hcz?Eojb{=&SYn!&&&{H6^Na9MDBa?FKY_gY{Yj0?m%1Y;&;W% zv9Iu!eMJ|61TPoTzI{plzkT`g&(lK^zNOhtKd*{F1Oj$P0}Njhdn|@o4L;1+aY;Q+ z{Ed!OEu*kxNryq`;V2)?6A5RX;)@lw6#ZfiFz%;me$_KQYwe!X4{_oxyLaTuXdxXw z8A%nnlApcG+wPeW*EZwMz+pN5i3T*cTtzPJ^`9)Wh{Muw;*h(fIHZq<2BoD!?oIes ztBaYv*6{Si@ZQ1V3;;A}7?OfC1PlYd(z+h!BPXM*=AkMY26_b_Jv?GUL|w}F?{Jo1 zeSDF1u8y#JJcy#uF?rC)c>G<%qkCZ=j&M62krjj3kM744_$Gk>ti=*MFxgbby$pDXP&yCR2#QJ@y2;2K}8Xd5+^`5ZYfX_ zfetQT?2k8^?BhGxirAMhCw7EHqGRP3q!408!z&&=`%%6Y%pKRodx{8hztynCcm9A4 z{+m1I5rCWH1-a99?t;hAR}fvPzvuQxBbT02v+6{9imZvUn)|Iy#aLS*FH*v6r=?Di7IK2gti>&>Y6u4skk*3zi%JHbaVK~RK} z@d`{Z*P~;lmU$I3j>T2x_=Vj()ir3V7(O9Bd|oQ#VYzEwa5P#(s{;;ZcF*KgPkiI4 z-{xm^n$lhbNKxeMpOW1>W6z}Fsa@p4hv}hgjd=WI?u{N-^-IFWM^^kC$Yt=3kP}FB z0_^lipnU+yp&~+lfY84q{t)2c34n7gD=3eU$d4ueCg7QQ$nVc_@N;l{J#BX%a3qVm ztN{JZ@cus|II>Nd`$+I{d^*QPRFkf^KLzm53XJYIx&dTn9L5Gg7j8)Bde zh}edpTQ=g41WH_gmMNj1_1faPTBMw{obL6pj<2;$_IOrstiiw zV5|lub~f|mTHLV9^Lo1w)>@tU2ALyV8bQB^FjRhmjwJH15uXLR|Ehl zFA0rkN_2txeQsVtqE&%A?I>n>peYam@)W?{G8gjb#Ma`9L~RQTdqy;~Of9>^V!HKp zIK4{oBICb(tLmrO)HNGOP(Q?u8;3OAaJN_2C}Rs}zM;)Y5DW|08kTUhrA0foxXRd; zU0s$%8NroZJ=5vC=pB}nP(rgeXWV(=&>HO9oEFzhfqXozv6ip;=s2jPG7X(&4vsD=eWk5+i>gldZ@QpdCzAkQjy&vY zHn=#qXy@$aqJO{`*ezDuHftfTi0q0tElqCpQ&JnWhiau1PU4`UHi?7xddgr}>_Cmw zW&>k4+^@{b{%j;o&!d{F)JOBZn=?%^j{EHVLK$GCQUKP$+>a+eB*~!W0m>s?H1wbJ zw~tsr;qaMo_+beL2y9(9Y<^a?5s$NM9q_ZNMFk#Usr;;Eat9%OCLG=> z;h-zPhDcB13C&!@6Iek|poR=2gk%LF^92cq=Y#ms6AlZHZiteizB(9CblK*paGlwd z2#6}WfRRcB)x?m>z_u+&e%4@sA;YpKWI9!Cvp{FE;g+J=G;B5-bspo?b*xM){n0G& zRC7K;)-HwGu;p8JkF|YwKJ3=jn!Z0dtxwLOhvC3b#?|6c>!zknJwIr)3%z!?L?on2 z<=NbAgZ*YhnaSm9Tj|MIi+n6QOmwIts7by+QkYc2VlPk*Ute(JAYH1_S9E53dgfK3|&nd}-nXzBIuP z+md9cP>^KB Date: Tue, 15 Sep 2020 23:48:12 +0300 Subject: [PATCH 03/15] [Security Solutions][Cases - Timeline] Fix bug when adding a timeline to a case (#76967) Co-authored-by: Gloria Hornero Co-authored-by: Elastic Machine --- .../cypress/integration/cases.spec.ts | 4 +- .../timeline_attach_to_case.spec.ts | 76 + .../security_solution/cypress/objects/case.ts | 7 +- .../cypress/objects/timeline.ts | 4 + .../cypress/screens/all_cases.ts | 6 + .../cypress/screens/create_new_case.ts | 3 +- .../cypress/screens/timeline.ts | 11 + .../cypress/tasks/create_new_case.ts | 12 + .../security_solution/cypress/tasks/login.ts | 9 + .../cypress/tasks/timeline.ts | 28 + .../public/cases/components/__mock__/form.ts | 7 + .../components/add_comment/index.test.tsx | 9 +- .../cases/components/add_comment/index.tsx | 26 +- .../cases/components/create/index.test.tsx | 15 +- .../public/cases/components/create/index.tsx | 22 +- .../user_action_tree/index.test.tsx | 69 +- .../user_action_tree/user_action_markdown.tsx | 21 +- .../insert_timeline_popover/index.test.tsx | 55 +- .../insert_timeline_popover/index.tsx | 23 +- .../use_insert_timeline.tsx | 40 +- .../case_and_timeline/data.json.gz | Bin 0 -> 3687 bytes .../case_and_timeline/mappings.json | 2616 +++++++++++++++++ 22 files changed, 2941 insertions(+), 122 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts create mode 100644 x-pack/test/security_solution_cypress/es_archives/case_and_timeline/data.json.gz create mode 100644 x-pack/test/security_solution_cypress/es_archives/case_and_timeline/mappings.json 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 9438c28f05fef..6194d6892d799 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts @@ -40,7 +40,7 @@ import { TIMELINE_DESCRIPTION, TIMELINE_QUERY, TIMELINE_TITLE } from '../screens import { goToCaseDetails, goToCreateNewCase } from '../tasks/all_cases'; import { openCaseTimeline } from '../tasks/case_details'; -import { backToCases, createNewCase } from '../tasks/create_new_case'; +import { backToCases, createNewCaseWithTimeline } from '../tasks/create_new_case'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; @@ -58,7 +58,7 @@ describe('Cases', () => { it('Creates a new case with timeline and opens the timeline', () => { loginAndWaitForPageWithoutDateRange(CASES_URL); goToCreateNewCase(); - createNewCase(case1); + createNewCaseWithTimeline(case1); backToCases(); cy.get(ALL_CASES_PAGE_TITLE).should('have.text', 'Cases Beta'); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts new file mode 100644 index 0000000000000..6af4d174b9583 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts @@ -0,0 +1,76 @@ +/* + * 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 { loginAndWaitForTimeline } from '../tasks/login'; +import { + attachTimelineToNewCase, + attachTimelineToExistingCase, + addNewCase, + selectCase, +} from '../tasks/timeline'; +import { DESCRIPTION_INPUT } from '../screens/create_new_case'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { caseTimeline, TIMELINE_CASE_ID } from '../objects/case'; + +describe('attach timeline to case', () => { + beforeEach(() => { + loginAndWaitForTimeline(caseTimeline.id); + }); + context('without cases created', () => { + before(() => { + esArchiverLoad('timeline'); + }); + + after(() => { + esArchiverUnload('timeline'); + }); + + it('attach timeline to a new case', () => { + attachTimelineToNewCase(); + + cy.location('origin').then((origin) => { + cy.get(DESCRIPTION_INPUT).should( + 'have.text', + `[${caseTimeline.title}](${origin}/app/security/timelines?timeline=(id:'${caseTimeline.id}',isOpen:!t))` + ); + }); + }); + + it('attach timeline to an existing case with no case', () => { + attachTimelineToExistingCase(); + addNewCase(); + + cy.location('origin').then((origin) => { + cy.get(DESCRIPTION_INPUT).should( + 'have.text', + `[${caseTimeline.title}](${origin}/app/security/timelines?timeline=(id:'${caseTimeline.id}',isOpen:!t))` + ); + }); + }); + }); + + context('with cases created', () => { + before(() => { + esArchiverLoad('case_and_timeline'); + }); + + after(() => { + esArchiverUnload('case_and_timeline'); + }); + + it('attach timeline to an existing case', () => { + attachTimelineToExistingCase(); + selectCase(TIMELINE_CASE_ID); + + cy.location('origin').then((origin) => { + cy.get(DESCRIPTION_INPUT).should( + 'have.text', + `[${caseTimeline.title}](${origin}/app/security/timelines?timeline=(id:'${caseTimeline.id}',isOpen:!t))` + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/objects/case.ts b/x-pack/plugins/security_solution/cypress/objects/case.ts index 12d3f925169af..084df31a604a3 100644 --- a/x-pack/plugins/security_solution/cypress/objects/case.ts +++ b/x-pack/plugins/security_solution/cypress/objects/case.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Timeline } from './timeline'; +import { Timeline, TimelineWithId } from './timeline'; export interface TestCase { name: string; @@ -21,10 +21,11 @@ export interface Connector { password: string; } -const caseTimeline: Timeline = { +export const caseTimeline: TimelineWithId = { title: 'SIEM test', description: 'description', query: 'host.name:*', + id: '0162c130-78be-11ea-9718-118a926974a4', }; export const case1: TestCase = { @@ -41,3 +42,5 @@ export const serviceNowConnector: Connector = { username: 'Username Name', password: 'password', }; + +export const TIMELINE_CASE_ID = '68248e00-f689-11ea-9ab2-59238b522856'; diff --git a/x-pack/plugins/security_solution/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts index 060a1376b46ce..ff7e80e5661ad 100644 --- a/x-pack/plugins/security_solution/cypress/objects/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/objects/timeline.ts @@ -9,3 +9,7 @@ export interface Timeline { description: string; query: string; } + +export interface TimelineWithId extends Timeline { + id: string; +} diff --git a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts index 4fa6b69eea7c3..dc0e764744f84 100644 --- a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts +++ b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +export const ALL_CASES_CASE = (id: string) => { + return `[data-test-subj="cases-table-row-${id}"]`; +}; + export const ALL_CASES_CLOSE_ACTION = '[data-test-subj="action-close"]'; export const ALL_CASES_CLOSED_CASES_COUNT = '[data-test-subj="closed-case-count"]'; @@ -14,6 +18,8 @@ export const ALL_CASES_COMMENTS_COUNT = '[data-test-subj="case-table-column-comm export const ALL_CASES_CREATE_NEW_CASE_BTN = '[data-test-subj="createNewCaseBtn"]'; +export const ALL_CASES_CREATE_NEW_CASE_TABLE_BTN = '[data-test-subj="cases-table-add-case"]'; + export const ALL_CASES_DELETE_ACTION = '[data-test-subj="action-delete"]'; export const ALL_CASES_NAME = '[data-test-subj="case-details-link"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts index 6e2beb78fff19..9431c054d96a4 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts @@ -6,8 +6,7 @@ export const BACK_TO_CASES_BTN = '[data-test-subj="backToCases"]'; -export const DESCRIPTION_INPUT = - '[data-test-subj="caseDescription"] [data-test-subj="textAreaInput"]'; +export const DESCRIPTION_INPUT = '[data-test-subj="textAreaInput"]'; export const INSERT_TIMELINE_BTN = '[data-test-subj="insert-timeline-button"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index fd41cd63fc090..bcb64fc947feb 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -4,8 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +export const ATTACH_TIMELINE_TO_NEW_CASE_ICON = '[data-test-subj="attach-timeline-case"]'; + +export const ATTACH_TIMELINE_TO_EXISTING_CASE_ICON = + '[data-test-subj="attach-timeline-existing-case"]'; + export const BULK_ACTIONS = '[data-test-subj="utility-bar-action-button"]'; +export const CASE = (id: string) => { + return `[data-test-subj="cases-table-row-${id}"]`; +}; + export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; @@ -25,6 +34,8 @@ export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; +export const OPEN_TIMELINE_ICON = '[data-test-subj="open-timeline-button"]'; + export const PIN_EVENT = '[data-test-subj="pin"]'; export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts index b2cde23a8dce2..1d5d240c5c53d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts @@ -29,6 +29,18 @@ export const createNewCase = (newCase: TestCase) => { }); cy.get(DESCRIPTION_INPUT).type(`${newCase.description} `, { force: true }); + cy.get(SUBMIT_BTN).click({ force: true }); + cy.get(LOADING_SPINNER).should('exist'); + cy.get(LOADING_SPINNER).should('not.exist'); +}; + +export const createNewCaseWithTimeline = (newCase: TestCase) => { + cy.get(TITLE_INPUT).type(newCase.name, { force: true }); + newCase.tags.forEach((tag) => { + cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true }); + }); + cy.get(DESCRIPTION_INPUT).type(`${newCase.description} `, { force: true }); + cy.get(INSERT_TIMELINE_BTN).click({ force: true }); cy.get(TIMELINE_SEARCHBOX).type(`${newCase.timeline.title}{enter}`); cy.get(TIMELINE).should('be.visible'); diff --git a/x-pack/plugins/security_solution/cypress/tasks/login.ts b/x-pack/plugins/security_solution/cypress/tasks/login.ts index ca23a1defd4f5..65f821ec5bfb7 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/login.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/login.ts @@ -5,6 +5,7 @@ */ import * as yaml from 'js-yaml'; +import { TIMELINE_FLYOUT_BODY } from '../screens/timeline'; /** * Credentials in the `kibana.dev.yml` config file will be used to authenticate @@ -143,3 +144,11 @@ export const loginAndWaitForPageWithoutDateRange = (url: string) => { cy.visit(url); cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); }; + +export const loginAndWaitForTimeline = (timelineId: string) => { + login(); + cy.viewport('macbook-15'); + cy.visit(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`); + cy.get('[data-test-subj="headerGlobalNav"]'); + cy.get(TIMELINE_FLYOUT_BODY).should('be.visible'); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 6fb8bb5e29ae5..cd8b197fc4dec 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ALL_CASES_CREATE_NEW_CASE_TABLE_BTN } from '../screens/all_cases'; import { BULK_ACTIONS, CLOSE_TIMELINE_BTN, @@ -28,6 +29,10 @@ import { TOGGLE_TIMELINE_EXPAND_EVENT, REMOVE_COLUMN, RESET_FIELDS, + ATTACH_TIMELINE_TO_NEW_CASE_ICON, + OPEN_TIMELINE_ICON, + ATTACH_TIMELINE_TO_EXISTING_CASE_ICON, + CASE, } from '../screens/timeline'; import { drag, drop } from '../tasks/common'; @@ -44,6 +49,20 @@ export const addNameToTimeline = (name: string) => { cy.get(TIMELINE_TITLE).should('have.attr', 'value', name); }; +export const addNewCase = () => { + cy.get(ALL_CASES_CREATE_NEW_CASE_TABLE_BTN).click(); +}; + +export const attachTimelineToNewCase = () => { + cy.get(TIMELINE_SETTINGS_ICON).click({ force: true }); + cy.get(ATTACH_TIMELINE_TO_NEW_CASE_ICON).click({ force: true }); +}; + +export const attachTimelineToExistingCase = () => { + cy.get(TIMELINE_SETTINGS_ICON).click({ force: true }); + cy.get(ATTACH_TIMELINE_TO_EXISTING_CASE_ICON).click({ force: true }); +}; + export const checkIdToggleField = () => { cy.get(ID_HEADER_FIELD).should('not.exist'); @@ -85,6 +104,11 @@ export const openTimelineInspectButton = () => { cy.get(TIMELINE_INSPECT_BUTTON).trigger('click', { force: true }); }; +export const openTimelineFromSettings = () => { + cy.get(TIMELINE_SETTINGS_ICON).click({ force: true }); + cy.get(OPEN_TIMELINE_ICON).click({ force: true }); +}; + export const openTimelineSettings = () => { cy.get(TIMELINE_SETTINGS_ICON).trigger('click', { force: true }); }; @@ -132,6 +156,10 @@ export const resetFields = () => { cy.get(RESET_FIELDS).click({ force: true }); }; +export const selectCase = (caseId: string) => { + cy.get(CASE(caseId)).click(); +}; + export const waitForTimelinesPanelToBeLoaded = () => { cy.get(TIMELINES_TABLE).should('exist'); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts b/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts index 96c1217577ff2..87f8f46affb52 100644 --- a/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts +++ b/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; +import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'; + jest.mock( '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' ); +jest.mock( + '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data' +); + export const mockFormHook = { isSubmitted: false, isSubmitting: false, @@ -41,3 +47,4 @@ export const getFormMock = (sampleData: any) => ({ }); export const useFormMock = useForm as jest.Mock; +export const useFormDataMock = useFormData as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx index f697ce443f2c5..a800bd690f710 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx @@ -15,6 +15,7 @@ import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; import { usePostComment } from '../../containers/use_post_comment'; import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; +import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'; // we don't have the types for waitFor just yet, so using "as waitFor" until when we do import { wait as waitFor } from '@testing-library/react'; @@ -23,10 +24,15 @@ jest.mock( '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' ); +jest.mock( + '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data' +); + jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'); jest.mock('../../containers/use_post_comment'); -export const useFormMock = useForm as jest.Mock; +const useFormMock = useForm as jest.Mock; +const useFormDataMock = useFormData as jest.Mock; const useInsertTimelineMock = useInsertTimeline as jest.Mock; const usePostCommentMock = usePostComment as jest.Mock; @@ -73,6 +79,7 @@ describe('AddComment ', () => { useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); usePostCommentMock.mockImplementation(() => defaultPostCommment); useFormMock.mockImplementation(() => ({ form: formHookMock })); + useFormDataMock.mockImplementation(() => [{ comment: sampleData.comment }]); jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx index 87bd7bb247056..ef13c87a92dbb 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx @@ -14,7 +14,7 @@ import { Case } from '../../containers/types'; import { MarkdownEditorForm } from '../../../common/components/markdown_editor/form'; import { InsertTimelinePopover } from '../../../timelines/components/timeline/insert_timeline_popover'; import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; -import { Form, useForm, UseField } from '../../../shared_imports'; +import { Form, useForm, UseField, useFormData } from '../../../shared_imports'; import * as i18n from './translations'; import { schema } from './schema'; @@ -46,23 +46,31 @@ export const AddComment = React.memo( forwardRef( ({ caseId, disabled, showLoading = true, onCommentPosted, onCommentSaving }, ref) => { const { isLoading, postComment } = usePostComment(caseId); + const { form } = useForm({ defaultValue: initialCommentValue, options: { stripEmptyFields: false }, schema, }); - const { getFormData, setFieldValue, reset, submit } = form; - const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline( - form, - 'comment' + + const fieldName = 'comment'; + const { setFieldValue, reset, submit } = form; + const [{ comment }] = useFormData({ form, watch: [fieldName] }); + + const onCommentChange = useCallback((newValue) => setFieldValue(fieldName, newValue), [ + setFieldValue, + ]); + + const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline( + comment, + onCommentChange ); const addQuote = useCallback( (quote) => { - const { comment } = getFormData(); - setFieldValue('comment', `${comment}${comment.length > 0 ? '\n\n' : ''}${quote}`); + setFieldValue(fieldName, `${comment}${comment.length > 0 ? '\n\n' : ''}${quote}`); }, - [getFormData, setFieldValue] + [comment, setFieldValue] ); useImperativeHandle(ref, () => ({ @@ -87,7 +95,7 @@ export const AddComment = React.memo( {isLoading && showLoading && }
{ useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); usePostCaseMock.mockImplementation(() => defaultPostCase); useFormMock.mockImplementation(() => ({ form: formHookMock })); + useFormDataMock.mockImplementation(() => [{ description: sampleData.description }]); jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); (useGetTags as jest.Mock).mockImplementation(() => ({ tags: sampleTags, diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index 31e6da4269ead..3c3cc95218b03 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -24,6 +24,7 @@ import { useForm, UseField, FormDataProvider, + useFormData, } from '../../../shared_imports'; import { usePostCase } from '../../containers/use_post_case'; import { schema } from './schema'; @@ -69,13 +70,18 @@ export const Create = React.memo(() => { options: { stripEmptyFields: false }, schema, }); - const { submit } = form; + + const fieldName = 'description'; + const { submit, setFieldValue } = form; + const [{ description }] = useFormData({ form, watch: [fieldName] }); + const { tags: tagOptions } = useGetTags(); const [options, setOptions] = useState( tagOptions.map((label) => ({ label, })) ); + useEffect( () => setOptions( @@ -85,10 +91,16 @@ export const Create = React.memo(() => { ), [tagOptions] ); - const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline( - form, - 'description' + + const onDescriptionChange = useCallback((newValue) => setFieldValue(fieldName, newValue), [ + setFieldValue, + ]); + + const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline( + description, + onDescriptionChange ); + const handleTimelineClick = useTimelineClick(); const onSubmit = useCallback(async () => { @@ -141,7 +153,7 @@ export const Create = React.memo(() => { { })); const formHookMock = getFormMock(sampleData); useFormMock.mockImplementation(() => ({ form: formHookMock })); + useFormDataMock.mockImplementation(() => [{ content: sampleData.content, comment: '' }]); jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); }); @@ -69,7 +70,8 @@ describe('UserActionTree ', () => { defaultProps.data.createdBy.username ); }); - it('Renders service now update line with top and bottom when push is required', () => { + + it('Renders service now update line with top and bottom when push is required', async () => { const ourActions = [ getUserAction(['pushed'], 'push-to-service'), getUserAction(['comment'], 'update'), @@ -87,6 +89,7 @@ describe('UserActionTree ', () => { }, caseUserActions: ourActions, }; + const wrapper = mount( @@ -94,10 +97,16 @@ describe('UserActionTree ', () => { ); + + await act(async () => { + wrapper.update(); + }); + expect(wrapper.find(`[data-test-subj="show-top-footer"]`).exists()).toBeTruthy(); expect(wrapper.find(`[data-test-subj="show-bottom-footer"]`).exists()).toBeTruthy(); }); - it('Renders service now update line with top only when push is up to date', () => { + + it('Renders service now update line with top only when push is up to date', async () => { const ourActions = [getUserAction(['pushed'], 'push-to-service')]; const props = { ...defaultProps, @@ -112,6 +121,7 @@ describe('UserActionTree ', () => { }, }, }; + const wrapper = mount( @@ -119,16 +129,22 @@ describe('UserActionTree ', () => { ); + + await act(async () => { + wrapper.update(); + }); + expect(wrapper.find(`[data-test-subj="show-top-footer"]`).exists()).toBeTruthy(); expect(wrapper.find(`[data-test-subj="show-bottom-footer"]`).exists()).toBeFalsy(); }); - it('Outlines comment when update move to link is clicked', () => { + it('Outlines comment when update move to link is clicked', async () => { const ourActions = [getUserAction(['comment'], 'create'), getUserAction(['comment'], 'update')]; const props = { ...defaultProps, caseUserActions: ourActions, }; + const wrapper = mount( @@ -136,6 +152,11 @@ describe('UserActionTree ', () => { ); + + await act(async () => { + wrapper.update(); + }); + expect( wrapper.find(`[data-test-subj="comment-create-action"]`).first().prop('idToOutline') ).toEqual(''); @@ -148,12 +169,13 @@ describe('UserActionTree ', () => { ).toEqual(ourActions[0].commentId); }); - it('Switches to markdown when edit is clicked and back to panel when canceled', () => { + it('Switches to markdown when edit is clicked and back to panel when canceled', async () => { const ourActions = [getUserAction(['comment'], 'create')]; const props = { ...defaultProps, caseUserActions: ourActions, }; + const wrapper = mount( @@ -161,6 +183,11 @@ describe('UserActionTree ', () => { ); + + await act(async () => { + wrapper.update(); + }); + expect( wrapper .find( @@ -168,14 +195,17 @@ describe('UserActionTree ', () => { ) .exists() ).toEqual(false); + wrapper .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-ellipses"]`) .first() .simulate('click'); + wrapper .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-pencil"]`) .first() .simulate('click'); + expect( wrapper .find( @@ -183,12 +213,14 @@ describe('UserActionTree ', () => { ) .exists() ).toEqual(true); + wrapper .find( `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-cancel-markdown"]` ) .first() .simulate('click'); + expect( wrapper .find( @@ -299,23 +331,35 @@ describe('UserActionTree ', () => { ); - wrapper - .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-ellipses"]`) - .first() - .simulate('click'); + + await act(async () => { + await waitFor(() => { + wrapper + .find( + `[data-test-subj="description-action"] [data-test-subj="property-actions-ellipses"]` + ) + .first() + .simulate('click'); + wrapper.update(); + }); + }); + wrapper .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-quote"]`) .first() .simulate('click'); + expect(setFieldValue).toBeCalledWith('comment', `> ${props.data.description} \n`); }); - it('Outlines comment when url param is provided', () => { + + it('Outlines comment when url param is provided', async () => { const commentId = 'neat-comment-id'; const ourActions = [getUserAction(['comment'], 'create')]; const props = { ...defaultProps, caseUserActions: ourActions, }; + jest.spyOn(routeData, 'useParams').mockReturnValue({ commentId }); const wrapper = mount( @@ -324,6 +368,11 @@ describe('UserActionTree ', () => { ); + + await act(async () => { + wrapper.update(); + }); + expect( wrapper.find(`[data-test-subj="comment-create-action"]`).first().prop('idToOutline') ).toEqual(commentId); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx index da081fea5eac0..ac2ad179ec60c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; import * as i18n from '../case_view/translations'; import { Markdown } from '../../../common/components/markdown'; -import { Form, useForm, UseField } from '../../../shared_imports'; +import { Form, useForm, UseField, useFormData } from '../../../shared_imports'; import { schema, Content } from './schema'; import { InsertTimelinePopover } from '../../../timelines/components/timeline/insert_timeline_popover'; import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; @@ -41,11 +41,20 @@ export const UserActionMarkdown = ({ options: { stripEmptyFields: false }, schema, }); - const { submit } = form; - const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline( - form, - 'content' + + const fieldName = 'content'; + const { submit, setFieldValue } = form; + const [{ content: contentFormValue }] = useFormData({ form, watch: [fieldName] }); + + const onContentChange = useCallback((newValue) => setFieldValue(fieldName, newValue), [ + setFieldValue, + ]); + + const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline( + contentFormValue, + onContentChange ); + const handleCancelAction = useCallback(() => { onChangeEditable(id); }, [id, onChangeEditable]); @@ -93,7 +102,7 @@ export const UserActionMarkdown = ({ return isEditable ? ( { - const reactRedux = jest.requireActual('react-redux'); - return { - ...reactRedux, - useDispatch: () => mockDispatch, - useSelector: jest - .fn() - .mockReturnValueOnce({ - timelineId: 'timeline-id', - timelineSavedObjectId: '34578-3497-5893-47589-34759', - timelineTitle: 'Timeline title', - }) - .mockReturnValue(null), - }; -}); -const mockLocation = { - pathname: '/apath', - hash: '', - search: '', - state: '', -}; const onTimelineChange = jest.fn(); -const defaultProps = { +const props = { isDisabled: false, onTimelineChange, }; describe('Insert timeline popover ', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should insert a timeline when passed in the router state', () => { - mount(); - expect(mockDispatch.mock.calls[0][0]).toEqual({ - payload: { id: 'timeline-id', show: false }, - type: 'x-pack/security_solution/local/timeline/SHOW_TIMELINE', - }); - expect(onTimelineChange).toBeCalledWith( - 'Timeline title', - '34578-3497-5893-47589-34759', - undefined - ); - expect(mockDispatch.mock.calls[1][0]).toEqual({ - payload: null, - type: 'x-pack/security_solution/local/timeline/SET_INSERT_TIMELINE', - }); - }); - it('should do nothing when router state', () => { - jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); - mount(); - expect(mockDispatch).toHaveBeenCalledTimes(0); - expect(onTimelineChange).toHaveBeenCalledTimes(0); + it('it renders', () => { + const wrapper = mount(); + expect(wrapper.find('[data-test-subj="insert-timeline-popover"]').exists()).toBeTruthy(); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/index.tsx index 0adf767308269..11ad54321da88 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/index.tsx @@ -5,16 +5,12 @@ */ import { EuiButtonIcon, EuiPopover, EuiSelectableOption, EuiToolTip } from '@elastic/eui'; -import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import React, { memo, useCallback, useMemo, useState } from 'react'; import { OpenTimelineResult } from '../../open_timeline/types'; import { SelectableTimeline } from '../selectable_timeline'; import * as i18n from '../translations'; -import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline'; import { TimelineType } from '../../../../../common/types/timeline'; -import { State } from '../../../../common/store'; -import { setInsertTimeline } from '../../../store/timeline/actions'; interface InsertTimelinePopoverProps { isDisabled: boolean; @@ -33,25 +29,8 @@ export const InsertTimelinePopoverComponent: React.FC = ({ hideUntitled = false, onTimelineChange, }) => { - const dispatch = useDispatch(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const insertTimeline = useSelector((state: State) => { - return timelineSelectors.selectInsertTimeline(state); - }); - useEffect(() => { - if (insertTimeline != null) { - dispatch(timelineActions.showTimeline({ id: insertTimeline.timelineId, show: false })); - onTimelineChange( - insertTimeline.timelineTitle, - insertTimeline.timelineSavedObjectId, - insertTimeline.graphEventId - ); - dispatch(setInsertTimeline(null)); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [insertTimeline, dispatch]); - const handleClosePopover = useCallback(() => { setIsPopoverOpen(false); }, []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx index c3bcd1c0ebe51..55c0709bd5543 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx @@ -5,38 +5,60 @@ */ import { isEmpty } from 'lodash/fp'; -import { useCallback, useState } from 'react'; +import { useCallback, useState, useEffect } from 'react'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { useBasePath } from '../../../../common/lib/kibana'; import { CursorPosition } from '../../../../common/components/markdown_editor'; -import { FormData, FormHook } from '../../../../shared_imports'; +import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline'; +import { setInsertTimeline } from '../../../store/timeline/actions'; -export const useInsertTimeline = (form: FormHook, fieldName: string) => { +export const useInsertTimeline = (value: string, onChange: (newValue: string) => void) => { const basePath = window.location.origin + useBasePath(); + const dispatch = useDispatch(); const [cursorPosition, setCursorPosition] = useState({ start: 0, end: 0, }); + + const insertTimeline = useSelector(timelineSelectors.selectInsertTimeline, shallowEqual); + const handleOnTimelineChange = useCallback( (title: string, id: string | null, graphEventId?: string) => { const builtLink = `${basePath}/app/security/timelines?timeline=(id:'${id}'${ !isEmpty(graphEventId) ? `,graphEventId:'${graphEventId}'` : '' },isOpen:!t)`; - const currentValue = form.getFormData()[fieldName]; + const newValue: string = [ - currentValue.slice(0, cursorPosition.start), + value.slice(0, cursorPosition.start), cursorPosition.start === cursorPosition.end ? `[${title}](${builtLink})` - : `[${currentValue.slice(cursorPosition.start, cursorPosition.end)}](${builtLink})`, - currentValue.slice(cursorPosition.end), + : `[${value.slice(cursorPosition.start, cursorPosition.end)}](${builtLink})`, + value.slice(cursorPosition.end), ].join(''); - form.setFieldValue(fieldName, newValue); + + onChange(newValue); }, - [basePath, cursorPosition, fieldName, form] + [value, onChange, basePath, cursorPosition] ); + const handleCursorChange = useCallback((cp: CursorPosition) => { setCursorPosition(cp); }, []); + // insertTimeline selector is defined to attached a timeline to a case outside of the case page. + // FYI, if you are in the case page we only use handleOnTimelineChange to attach a timeline to a case. + useEffect(() => { + if (insertTimeline != null && value != null) { + dispatch(timelineActions.showTimeline({ id: insertTimeline.timelineId, show: false })); + handleOnTimelineChange( + insertTimeline.timelineTitle, + insertTimeline.timelineSavedObjectId, + insertTimeline.graphEventId + ); + dispatch(setInsertTimeline(null)); + } + }, [insertTimeline, dispatch, handleOnTimelineChange, value]); + return { cursorPosition, handleCursorChange, diff --git a/x-pack/test/security_solution_cypress/es_archives/case_and_timeline/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/case_and_timeline/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..5838d18e1c7dd2c5907cc5f5ef9f55d4cfe2a8d8 GIT binary patch literal 3687 zcmV-t4w&&DiwFP!000026YX7FbK5o+e)q57@~Pc1Y2Gi|m+huW>uKF)Yd76#-H|~O zlu+}c5TIivAn#F*O$VW!u*Q-}z+5J$B6Js>V06}T;4rZACXwJT% zDB7|sE31!ZFoYRQe2D3wKg!lW7u^+m%6WM6!wLKMaB}jgKLha$_;3`!5abcrg>{>^ z$){&CS+cdmF003DGX@a*8JdxTlz25oSfGE&6bj`o1pd@N#Ja=~23`b%HxQ6KYY?lE zqXQEV^W}b9GGt}J6<3Ru?acNPw+_SY0RZbNimke?W*WAkxUTxV;9RulGj?=>F(9m0 zQ#3_V3`w!CRApqiBgK?0L;ZbsuEt=7B?2QDLz2x$&96IwyE#LHNq~?Hc{zdjEzB;! ztkGL&32@10#IMpkA+3Hk!yV(Xa@uT-!F^XNVlq-{jSuw^B8>4n=#w=25zMJbyC^mv zh}gI*%GSPP544Mu4w8k8-ck<3EWy7aGW`ywZxD=vY7MoCxsa|>0-~-?3<*KW1h=Jw zr|AbOw08*SAVMGcS>W8D149sD*tsr(V)zQom<|a#A*?F0Yg@nXd}2BwyZz)e$nJ7- zz-$&FpL1cHV=#eZl7R#RpS6H!WWgYGO+%4v#|tD?g+Ov`)uEpp;A)m@8^GA*fNi1M zI&rhvd115=jO*&1!it^4pOZ^JgOEW?lTE{P^yMF;uH^IBgW1pFD@yv7$wSjxGR6eN zvu;kivSzru3wYaxdrO?I!&DC_@lY{b7n+}q#1&a}sLVHsS?MQbQyZC2H0~g|=vRZ5 zp;@tNg*^FnsrU0I>%KMZWsYH1tZsEfX@(}K4fGefscuO!E4yM(`Lf5wg)c9zmK|4x zDB669$+9$CeXLma>Eq_27mzWZZ#>D;e1^v}DibP~JLJQ4BsNY~gXvzTq(=z(d)jQ8 zcqu<%lRZsX?g~G8TZPVuSFXc5FJuOiJxxUgxXX?1{ZC=|cCF>lo=n7S6QdlHO3!ZAw5-kk82YKpU?-o#T1V!VE5 zY7fVOz|yl_VOkt@YNYXBsfWj1oaiP|NsFKUE+tR-O%h1jzz`?tO4DE94y-m}$paX! zv?pl*|H3QHvpJb^Xf~U(+?#ha#!@jww|fKd?QWW#r0H!J^kO!h(Np|uOyB0owZ5~j zzkzHcp}IXuaI?_iZEhuC^(0WLgE76iKAKTxZyIZl(AIq@J!#Cclx_o4;|%Y1kh!k+ zRVZuC*k(-imB(EXJ$v;0eXd-)`yTNQ5d_7SUB`KXr@=flDMu1>tRj@ak)m3fuj<@^ zgnM)#w}p&b>u5(dqdZQU@n4}WyJFDy5Clbpj4doli4>xUKo+rG{ zI+O_WC=W}7A4{2fuyB%66di$9dBZv=rD=+Z%stA3k|{7NqXPpWplG88jj`?Ew3tV` z%DK)Iil0HIXqY)L9ZNGUM==%CSRL`7IhTF}=r(+@nO_5Mv-A$pC9Y4^Fm?6|NG9!D zsR)xgaox5ow`mjz$Os={Sh}HGnqjNm_n}sPe}aM2O6a6bKbyrfEaqzFjF z<#~Dj`a+-{5vhx`w8C|Kx|-{(j%Xf^^=Zhqwd)sI)JLW(>xynO0&MIc~AHMDzHH^|3OvbHM?DG~- zEowoww$nvZ>t;{{d{=9uHnPST8Mc~=Z~V||6% zGx6SH?z1J{_aNjJ{wwH@nf41_)B(_regKxdDYh*w^W~$9^dpKQaAwMi@YMzIQ9@EY z{g-f_5Eu!xLHPMn_@AIEW7Qa2Ukl$*fy3XR_X8qlrf$o+C4BY6_g5D`o(d7Vg~E5x zzfHduUQIKK<1>e1Rn}d{mQ_m^EMsabj*k!zluj^&a%XV_HkP0lF5^s&65s zonLC;*ysoJLV||!RG@QHMrxde=$9mJ>yD_Tnc!7mHmWMj)nyVJ5eE0Sod~ zjzgMJh4mrLsJ5E&+`{&Z)q&vC>R&`~sn?ZE#DjpVIqfeo`)d&bM>bDTAJ$Uw!WtRj*T!Kla1(; zV(43XS0U?{^usT7F;rD&=fV0s!i%lc+jn{1XH`Ro;<)hm8~LCTomJSopzBhMX|`iZ znqvi$0X-;rj$=qF&=e<7RTbFErdgK1^Ge-Nv$r@IT`8#VU_$xr3qL{fA9x^!_E9IiHsLShDiPp$sZ#5e=BA4O$(!SB~yp$CVf+b z+!mIDjG3zCw1dLgS>YT)c|2WbloEdNs^cZd_H8Lr6l@ZUp&42)o*z)3JwpO$h~z&f z;8_HJ&)ols^SxjJ$lL{3<_D7^*7667%nhgID>g?ObF-zx>`ye z`O~7BhF;ZDHc)=13`c*NKO+BOe)KRe)rvp(h!x5=fqEX_5zl@A|q098=#Z z4qkUepB4o#esi15<+d#vE`C}Leh}Bk#WzjaKPFzM^<|_*49hVCpc#^G`t+L~>XPGX zfux46W(8honwGN*9F7M7s)lAMS}AMXN%$LNzaY~~?m_cEe0WkCgq}{#Q5?frn+8!W zRngdpAq_I5L3T}p1fFhLrlw1w>RXcGtD)rBx++;dG-2RE(^l;xr9oboXYCPr5c`?( zAo{-ZAdsHrArCU-L54iYkOvv^AVVHx$b$@dkRcB;IIIhCIlS2YJps zh%@9tj+F;_`r7~-oZ?4>zFS)w5+57oUqn*6@=?c@{&FQx$$8cHbNqKehUCYP{5V4L z!?vKQX@)7;rs_(DW~!2_0#DK`E%a2N+M44ZDfv;|== Date: Tue, 15 Sep 2020 16:47:09 -0500 Subject: [PATCH 04/15] [Enterprise Search] Add flag to restrict width of layout (#77539) * [Enterprise Search] Add flag to restrict with of layout This PR adds a boolean flag to restrict the width of the Enterprise Search layout file for use in Workplace Search. * Add tests --- .../public/applications/shared/layout/layout.test.tsx | 9 ++++++++- .../public/applications/shared/layout/layout.tsx | 7 +++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx index 4053f2f4bb613..623e6e47167d2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiPageSideBar, EuiButton } from '@elastic/eui'; +import { EuiPageSideBar, EuiButton, EuiPageBody } from '@elastic/eui'; import { Layout, INavContext } from './layout'; @@ -15,6 +15,13 @@ describe('Layout', () => { const wrapper = shallow(); expect(wrapper.find('.enterpriseSearchLayout')).toHaveLength(1); + expect(wrapper.find(EuiPageBody).prop('restrictWidth')).toBeFalsy(); + }); + + it('passes the restrictWidth prop', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiPageBody).prop('restrictWidth')).toEqual(true); }); it('renders navigation', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx index b4497140b65b7..e122c4d5cfdfa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx @@ -14,6 +14,7 @@ import './layout.scss'; interface ILayoutProps { navigation: React.ReactNode; + restrictWidth?: boolean; } export interface INavContext { @@ -21,7 +22,7 @@ export interface INavContext { } export const NavContext = React.createContext({}); -export const Layout: React.FC = ({ children, navigation }) => { +export const Layout: React.FC = ({ children, navigation, restrictWidth }) => { const [isNavOpen, setIsNavOpen] = useState(false); const toggleNavigation = () => setIsNavOpen(!isNavOpen); const closeNavigation = () => setIsNavOpen(false); @@ -54,7 +55,9 @@ export const Layout: React.FC = ({ children, navigation }) => { {navigation} - {children} + + {children} + ); }; From 8b001dc913b2f5ac415f442c441e224f4ad6b316 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 15 Sep 2020 18:26:05 -0400 Subject: [PATCH 05/15] [Maps] Add DynamicStyleProperty#getMbPropertyName and DynamicStyleProperty#getMbPropertyValue (#77366) --- x-pack/plugins/maps/common/constants.ts | 4 ++ .../maps/public/classes/sources/source.ts | 4 +- .../dynamic_color_property.test.tsx | 6 +- .../properties/dynamic_icon_property.test.tsx | 4 +- .../dynamic_orientation_property.ts | 22 +++++--- .../properties/dynamic_size_property.test.tsx | 4 +- .../properties/dynamic_size_property.tsx | 4 +- .../properties/dynamic_style_property.tsx | 55 ++++++++++++++++++- .../properties/dynamic_text_property.ts | 18 +++--- .../vector/properties/style_property.ts | 15 +++-- .../classes/styles/vector/vector_style.tsx | 29 ++++------ 11 files changed, 110 insertions(+), 55 deletions(-) diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 3589ec41e4db3..d72d04d2a1843 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -263,3 +263,7 @@ export enum MB_LOOKUP_FUNCTION { GET = 'get', FEATURE_STATE = 'feature-state', } + +export type RawValue = string | number | boolean | undefined | null; + +export type FieldFormatter = (value: RawValue) => string | number; diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 4a050cc3d7d19..7e7a7bd8f049d 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -12,7 +12,7 @@ import { Adapters } from 'src/plugins/inspector/public'; import { copyPersistentState } from '../../reducers/util'; import { IField } from '../fields/field'; -import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; +import { FieldFormatter, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; import { AbstractSourceDescriptor } from '../../../common/descriptor_types'; import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view'; @@ -37,8 +37,6 @@ export type PreIndexedShape = { path: string; }; -export type FieldFormatter = (value: string | number | null | undefined | boolean) => string; - export interface ISource { destroy(): void; getDisplayName(): Promise; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx index de8f3b5c09175..c9188a0a19b0d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx @@ -15,7 +15,7 @@ import { shallow } from 'enzyme'; import { Feature, Point } from 'geojson'; import { DynamicColorProperty } from './dynamic_color_property'; -import { COLOR_MAP_TYPE, VECTOR_STYLES } from '../../../../../common/constants'; +import { COLOR_MAP_TYPE, RawValue, VECTOR_STYLES } from '../../../../../common/constants'; import { mockField, MockLayer, MockStyle } from './__tests__/test_util'; import { ColorDynamicOptions } from '../../../../../common/descriptor_types'; import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; @@ -28,7 +28,7 @@ const makeProperty = (options: ColorDynamicOptions, style?: MockStyle, field?: I field ? field : mockField, (new MockLayer(style ? style : new MockStyle()) as unknown) as IVectorLayer, () => { - return (value: string | number | undefined) => value + '_format'; + return (value: RawValue) => value + '_format'; } ); }; @@ -273,7 +273,7 @@ describe('supportsFieldMeta', () => { null, (new MockLayer(new MockStyle()) as unknown) as IVectorLayer, () => { - return (value: string | number | undefined) => value + '_format'; + return (value: RawValue) => value + '_format'; } ); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx index 06987ab8bcc48..2f9e4709c1c0b 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx @@ -13,7 +13,7 @@ jest.mock('../components/vector_style_editor', () => ({ })); import React from 'react'; -import { VECTOR_STYLES } from '../../../../../common/constants'; +import { RawValue, VECTOR_STYLES } from '../../../../../common/constants'; // @ts-ignore import { DynamicIconProperty } from './dynamic_icon_property'; import { mockField, MockLayer } from './__tests__/test_util'; @@ -33,7 +33,7 @@ const makeProperty = (options: Partial, field: IField = mock field, mockVectorLayer, () => { - return (value: string | number | undefined) => value + '_format'; + return (value: RawValue) => value + '_format'; } ); }; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts index dd976027a50f2..192fa1f4db6e0 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts @@ -5,25 +5,29 @@ */ import { Map as MbMap } from 'mapbox-gl'; -import { DynamicStyleProperty } from './dynamic_style_property'; -import { getComputedFieldName } from '../style_util'; -import { VECTOR_STYLES } from '../../../../../common/constants'; +import { DynamicStyleProperty, getNumericalMbFeatureStateValue } from './dynamic_style_property'; import { OrientationDynamicOptions } from '../../../../../common/descriptor_types'; +import { RawValue } from '../../../../../common/constants'; export class DynamicOrientationProperty extends DynamicStyleProperty { syncIconRotationWithMb(symbolLayerId: string, mbMap: MbMap) { if (this._field && this._field.isValid()) { - const targetName = this._field.supportsAutoDomain() - ? getComputedFieldName(VECTOR_STYLES.ICON_ORIENTATION, this.getFieldName()) - : this._field.getName(); - // Using property state instead of feature-state because layout properties do not support feature-state - mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', ['coalesce', ['get', targetName], 0]); + const targetName = this.getMbPropertyName(); + mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', [ + 'coalesce', + [this.getMbLookupFunction(), targetName], + 0, + ]); } else { mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', 0); } } - supportsMbFeatureState() { + supportsMbFeatureState(): boolean { return false; } + + getMbPropertyValue(rawValue: RawValue): RawValue { + return getNumericalMbFeatureStateValue(rawValue); + } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx index c5298067f6cbe..b4244cf7829c4 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx @@ -15,7 +15,7 @@ import { shallow } from 'enzyme'; // @ts-ignore import { DynamicSizeProperty } from './dynamic_size_property'; -import { VECTOR_STYLES } from '../../../../../common/constants'; +import { RawValue, VECTOR_STYLES } from '../../../../../common/constants'; import { IField } from '../../../fields/field'; import { Map as MbMap } from 'mapbox-gl'; import { SizeDynamicOptions } from '../../../../../common/descriptor_types'; @@ -48,7 +48,7 @@ const makeProperty = ( field, (new MockLayer(mockStyle) as unknown) as IVectorLayer, () => { - return (value: string | number | undefined) => value + '_format'; + return (value: RawValue) => value + '_format'; }, false ); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx index 35c830f3cb5e3..4e75a61539ad9 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx @@ -7,7 +7,7 @@ import _ from 'lodash'; import React from 'react'; import { Map as MbMap } from 'mapbox-gl'; -import { DynamicStyleProperty, FieldFormatter } from './dynamic_style_property'; +import { DynamicStyleProperty } from './dynamic_style_property'; import { OrdinalLegend } from '../components/legend/ordinal_legend'; import { makeMbClampedNumberExpression } from '../style_util'; import { @@ -16,7 +16,7 @@ import { SMALL_MAKI_ICON_SIZE, // @ts-expect-error } from '../symbol_utils'; -import { MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants'; +import { FieldFormatter, MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants'; import { SizeDynamicOptions } from '../../../../../common/descriptor_types'; import { IField } from '../../../fields/field'; import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index f6ab052497723..2bc819daeea90 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -15,6 +15,8 @@ import { SOURCE_META_DATA_REQUEST_ID, STYLE_TYPE, VECTOR_STYLES, + RawValue, + FieldFormatter, } from '../../../../../common/constants'; import { OrdinalFieldMetaPopover } from '../components/field_meta/ordinal_field_meta_popover'; import { CategoricalFieldMetaPopover } from '../components/field_meta/categorical_field_meta_popover'; @@ -28,6 +30,7 @@ import { IField } from '../../../fields/field'; import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; import { IJoin } from '../../../joins/join'; import { IVectorStyle } from '../vector_style'; +import { getComputedFieldName } from '../style_util'; export interface IDynamicStyleProperty extends IStyleProperty { getFieldMetaOptions(): FieldMetaOptions; @@ -46,9 +49,16 @@ export interface IDynamicStyleProperty extends IStyleProperty { pluckOrdinalStyleMetaFromFeatures(features: Feature[]): RangeFieldMeta | null; pluckCategoricalStyleMetaFromFeatures(features: Feature[]): CategoryFieldMeta | null; getValueSuggestions(query: string): Promise; -} -export type FieldFormatter = (value: string | number | undefined) => string | number; + // Returns the name that should be used for accessing the data from the mb-style rule + // Depending on + // - whether the field is used for labeling, icon-orientation, or other properties (color, size, ...), `feature-state` and or `get` is used + // - whether the field was run through a field-formatter, a new dynamic field is created with the formatted-value + // The combination of both will inform what field-name (e.g. the "raw" field name from the properties, the "computed field-name" for an on-the-fly created property (e.g. for feature-state or field-formatting). + // todo: There is an existing limitation to .mvt backed sources, where the field-formatters are not applied. Here, the raw-data needs to be accessed. + getMbPropertyName(): string; + getMbPropertyValue(value: RawValue): RawValue; +} export class DynamicStyleProperty extends AbstractStyleProperty @@ -313,7 +323,7 @@ export class DynamicStyleProperty }; } - formatField(value: string | number | undefined): string | number { + formatField(value: RawValue): string | number { if (this.getField()) { const fieldName = this.getFieldName(); const fieldFormatter = this._getFieldFormatter(fieldName); @@ -345,4 +355,43 @@ export class DynamicStyleProperty /> ); } + + getMbPropertyName() { + if (!this._field) { + return ''; + } + + let targetName; + if (this.supportsMbFeatureState()) { + // Base case for any properties that can support feature-state (e.g. color, size, ...) + // They just re-use the original property-name + targetName = this._field.getName(); + } else { + if (this._field.canReadFromGeoJson() && this._field.supportsAutoDomain()) { + // Geojson-sources can support rewrite + // e.g. field-formatters will create duplicate field + targetName = getComputedFieldName(this.getStyleName(), this._field.getName()); + } else { + // Non-geojson sources (e.g. 3rd party mvt or ES-source as mvt) + targetName = this._field.getName(); + } + } + return targetName; + } + + getMbPropertyValue(rawValue: RawValue): RawValue { + // Maps only uses feature-state for numerical values. + // `supportsMbFeatureState` will only return true when the mb-style rule does a feature-state lookup on a numerical value + // Calling `isOrdinal` would be equivalent. + return this.supportsMbFeatureState() ? getNumericalMbFeatureStateValue(rawValue) : rawValue; + } +} + +export function getNumericalMbFeatureStateValue(value: RawValue) { + if (typeof value !== 'string') { + return value; + } + + const valueAsFloat = parseFloat(value); + return isNaN(valueAsFloat) ? null : valueAsFloat; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts index d55a6e1cfb444..ec79d71eb7587 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts @@ -6,18 +6,18 @@ import { Map as MbMap } from 'mapbox-gl'; import { DynamicStyleProperty } from './dynamic_style_property'; -import { getComputedFieldName } from '../style_util'; import { LabelDynamicOptions } from '../../../../../common/descriptor_types'; +import { RawValue } from '../../../../../common/constants'; export class DynamicTextProperty extends DynamicStyleProperty { syncTextFieldWithMb(mbLayerId: string, mbMap: MbMap) { if (this._field && this._field.isValid()) { - // Fields that support auto-domain are normalized with a field-formatter and stored into a computed-field - // Otherwise, the raw value is just carried over and no computed field is created. - const targetName = this._field.supportsAutoDomain() - ? getComputedFieldName(this._styleName, this.getFieldName()) - : this._field.getName(); - mbMap.setLayoutProperty(mbLayerId, 'text-field', ['coalesce', ['get', targetName], '']); + const targetName = this.getMbPropertyName(); + mbMap.setLayoutProperty(mbLayerId, 'text-field', [ + 'coalesce', + [this.getMbLookupFunction(), targetName], + '', + ]); } else { mbMap.setLayoutProperty(mbLayerId, 'text-field', null); } @@ -34,4 +34,8 @@ export class DynamicTextProperty extends DynamicStyleProperty { isDynamic(): boolean; isComplete(): boolean; - formatField(value: string | number | undefined): string | number; + formatField(value: RawValue): string | number; getStyleName(): VECTOR_STYLES; getOptions(): T; renderLegendDetailRow(legendProps: LegendProps): ReactElement | null; @@ -53,9 +53,14 @@ export class AbstractStyleProperty implements IStyleProperty { return true; } - formatField(value: string | number | undefined): string | number { - // eslint-disable-next-line eqeqeq - return value == undefined ? '' : value; + formatField(value: RawValue): string | number { + if (typeof value === 'undefined' || value === null) { + return ''; + } else if (typeof value === 'boolean') { + return value.toString(); + } else { + return value; + } } getStyleName(): VECTOR_STYLES { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx index 956522524a2eb..1244c53afe9a6 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx @@ -29,7 +29,7 @@ import { import { StyleMeta } from './style_meta'; import { VectorIcon } from './components/legend/vector_icon'; import { VectorStyleLegend } from './components/legend/vector_style_legend'; -import { getComputedFieldName, isOnlySingleFeatureType } from './style_util'; +import { isOnlySingleFeatureType } from './style_util'; import { StaticStyleProperty } from './properties/static_style_property'; import { DynamicStyleProperty } from './properties/dynamic_style_property'; import { DynamicSizeProperty } from './properties/dynamic_size_property'; @@ -82,11 +82,6 @@ const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT]; const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING]; const POLYGONS = [GEO_JSON_TYPE.POLYGON, GEO_JSON_TYPE.MULTI_POLYGON]; -function getNumericalMbFeatureStateValue(value: string) { - const valueAsFloat = parseFloat(value); - return isNaN(valueAsFloat) ? null : valueAsFloat; -} - export interface IVectorStyle extends IStyle { getAllStyleProperties(): Array>; getDynamicPropertiesArray(): Array>; @@ -618,21 +613,17 @@ export class VectorStyle implements IVectorStyle { for (let j = 0; j < dynamicStyleProps.length; j++) { const dynamicStyleProp = dynamicStyleProps[j]; - const name = dynamicStyleProp.getFieldName(); - const computedName = getComputedFieldName(dynamicStyleProp.getStyleName(), name); - const rawValue = feature.properties ? feature.properties[name] : undefined; + const targetMbName = dynamicStyleProp.getMbPropertyName(); + const rawValue = feature.properties + ? feature.properties[dynamicStyleProp.getFieldName()] + : undefined; + const targetMbValue = dynamicStyleProp.getMbPropertyValue(rawValue); if (dynamicStyleProp.supportsMbFeatureState()) { - tmpFeatureState[name] = getNumericalMbFeatureStateValue(rawValue); // the same value will be potentially overridden multiple times, if the name remains identical + tmpFeatureState[targetMbName] = targetMbValue; // the same value will be potentially overridden multiple times, if the name remains identical } else { - // in practice, a new system property will only be created for: - // - label text: this requires the value to be formatted first. - // - icon orientation: this is a lay-out property which do not support feature-state (but we're still coercing to a number) - - const formattedValue = dynamicStyleProp.isOrdinal() - ? getNumericalMbFeatureStateValue(rawValue) - : dynamicStyleProp.formatField(rawValue); - - if (feature.properties) feature.properties[computedName] = formattedValue; + if (feature.properties) { + feature.properties[targetMbName] = targetMbValue; + } } } tmpFeatureIdentifier.source = mbSourceId; From e9a4555623ad55120085a1f9bca3774116eef07b Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 15 Sep 2020 18:27:39 -0400 Subject: [PATCH 06/15] [Lens] Remove dynamic names in telemetry fields (#76988) Co-authored-by: Elastic Machine --- .../editor_frame_service/editor_frame/suggestion_panel.tsx | 1 - .../dimension_panel/dimension_editor.tsx | 3 --- 2 files changed, 4 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index f1dc3fa306d15..e6503cb793a8e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -338,7 +338,6 @@ export function SuggestionPanel({ if (lastSelectedSuggestion === index) { rollbackToCurrentVisualization(); } else { - trackSuggestionEvent(`position_${index}_of_${suggestions.length}`); setLastSelectedSuggestion(index); switchToSuggestion(dispatch, suggestion); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 98e9389a85819..153757ac37da1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -213,9 +213,6 @@ export function DimensionEditor(props: DimensionEditorProps) { previousColumn: selectedColumn, }); - trackUiEvent( - `indexpattern_dimension_operation_from_${selectedColumn.operationType}_to_${operationType}` - ); setState( changeColumn({ state, From 0752de7b1091cebb250ff2519a8631555abde47e Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 15 Sep 2020 16:44:06 -0700 Subject: [PATCH 07/15] Skip flaky Events Viewer Cypress test Signed-off-by: Tyler Smalley --- .../cypress/integration/events_viewer.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 5f2de69689865..d193330dc54ff 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 @@ -47,7 +47,8 @@ const defaultHeadersInDefaultEcsCategory = [ { id: 'destination.ip' }, ]; -describe('Events Viewer', () => { +// https://github.com/elastic/kibana/issues/70757 +describe.skip('Events Viewer', () => { context('Fields rendering', () => { before(() => { loginAndWaitForPage(HOSTS_URL); From e667f2fa8d637e76bb726109afa2731b1990a394 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 15 Sep 2020 18:28:38 -0700 Subject: [PATCH 08/15] [UBI] Copy license to /licenses folder (#77563) Requirement for OpenShift certification https://redhat-connect.gitbook.io/partner-guide-for-red-hat-openshift-and-container/program-on-boarding/technical-prerequisites#licenses-requirements Signed-off-by: Tyler Smalley --- .../tasks/os_packages/docker_generator/templates/Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile index d235bfe9d6fbc..24649a52b729b 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile @@ -85,7 +85,6 @@ RUN groupadd --gid 1000 kibana && \ useradd --uid 1000 --gid 1000 \ --home-dir /usr/share/kibana --no-create-home \ kibana -USER kibana LABEL org.label-schema.build-date="{{dockerBuildDate}}" \ org.label-schema.license="{{license}}" \ @@ -115,8 +114,13 @@ LABEL name="Kibana" \ release="1" \ summary="Kibana" \ description="Your window into the Elastic Stack." + +RUN mkdir /licenses && \ + cp LICENSE.txt /licenses/LICENSE {{/ubi}} +USER kibana + ENTRYPOINT ["/usr/local/bin/dumb-init", "--"] CMD ["/usr/local/bin/kibana-docker"] From ab92bbb726b806f4f0c1daa6320baf61fbb1bf96 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Wed, 16 Sep 2020 09:17:05 +0200 Subject: [PATCH 09/15] Move core config service to `kbn/config` package (#76874) * move deprecations and raw loader to package * move config service to package * start to adapt the usages * adapt yet more usages * update generated doc * move logging types to `@kbn/logging` * update generated doc * add yarn.lock symlink * merge @kbn-logging PR * adapt Env.createDefault * update generated doc * remove mock exports from the main entrypoint to avoid importing it in production code * use dynamic require to import `REPO_ROOT` from bootstrap file * move logger mock to kbn-logging package * address review comments * import PublicMethodOf from kbn/utility-types * fix import conflict * update generated doc * use the @kbn/std package * update generated doc * adapt plugin service mock --- ...-plugin-core-public.environmentmode.dev.md | 11 -- ...bana-plugin-core-public.environmentmode.md | 21 --- ...plugin-core-public.environmentmode.name.md | 11 -- ...plugin-core-public.environmentmode.prod.md | 11 -- .../core/public/kibana-plugin-core-public.md | 2 - ...a-plugin-core-public.packageinfo.branch.md | 11 -- ...plugin-core-public.packageinfo.buildnum.md | 11 -- ...plugin-core-public.packageinfo.buildsha.md | 11 -- ...ana-plugin-core-public.packageinfo.dist.md | 11 -- .../kibana-plugin-core-public.packageinfo.md | 23 --- ...-plugin-core-public.packageinfo.version.md | 11 -- ...na-plugin-core-server.configdeprecation.md | 18 --- ...in-core-server.configdeprecationfactory.md | 36 ----- ...-server.configdeprecationfactory.rename.md | 36 ----- ...configdeprecationfactory.renamefromroot.md | 39 ----- ...-server.configdeprecationfactory.unused.md | 35 ---- ...configdeprecationfactory.unusedfromroot.md | 37 ----- ...gin-core-server.configdeprecationlogger.md | 13 -- ...n-core-server.configdeprecationprovider.md | 28 ---- .../kibana-plugin-core-server.configpath.md | 12 -- ...-plugin-core-server.environmentmode.dev.md | 11 -- ...bana-plugin-core-server.environmentmode.md | 21 --- ...plugin-core-server.environmentmode.name.md | 11 -- ...plugin-core-server.environmentmode.prod.md | 11 -- .../kibana-plugin-core-server.logger.debug.md | 25 --- .../kibana-plugin-core-server.logger.error.md | 25 --- .../kibana-plugin-core-server.logger.fatal.md | 25 --- .../kibana-plugin-core-server.logger.get.md | 33 ---- .../kibana-plugin-core-server.logger.info.md | 25 --- .../kibana-plugin-core-server.logger.md | 26 --- .../kibana-plugin-core-server.logger.trace.md | 25 --- .../kibana-plugin-core-server.logger.warn.md | 25 --- ...na-plugin-core-server.loggerfactory.get.md | 24 --- ...kibana-plugin-core-server.loggerfactory.md | 20 --- .../kibana-plugin-core-server.logmeta.md | 13 -- .../core/server/kibana-plugin-core-server.md | 10 -- ...a-plugin-core-server.packageinfo.branch.md | 11 -- ...plugin-core-server.packageinfo.buildnum.md | 11 -- ...plugin-core-server.packageinfo.buildsha.md | 11 -- ...ana-plugin-core-server.packageinfo.dist.md | 11 -- .../kibana-plugin-core-server.packageinfo.md | 23 --- ...-plugin-core-server.packageinfo.version.md | 11 -- ...ver.pluginconfigdescriptor.deprecations.md | 2 +- ...ugin-core-server.pluginconfigdescriptor.md | 2 +- ...n-core-server.pluginmanifest.configpath.md | 2 +- ...ibana-plugin-core-server.pluginmanifest.md | 2 +- package.json | 2 + packages/kbn-config/README.md | 3 + .../kbn-config}/__fixtures__/config.yml | 0 .../kbn-config}/__fixtures__/config_flat.yml | 0 .../__fixtures__/en_var_ref_config.yml | 0 .../kbn-config}/__fixtures__/one.yml | 0 .../kbn-config}/__fixtures__/two.yml | 0 packages/kbn-config/package.json | 30 ++++ .../kbn-config/src}/__mocks__/env.ts | 0 .../__snapshots__/config_service.test.ts.snap | 0 .../src}/__snapshots__/env.test.ts.snap | 0 .../kbn-config/src}/apply_argv.test.ts | 0 .../kbn-config/src}/config.mock.ts | 0 .../kbn-config/src}/config.test.ts | 0 .../kbn-config/src}/config.ts | 2 +- .../kbn-config/src}/config_service.mock.ts | 0 .../src}/config_service.test.mocks.ts | 2 +- .../kbn-config/src}/config_service.test.ts | 34 ++-- .../kbn-config/src}/config_service.ts | 7 +- .../deprecation/apply_deprecations.test.ts | 0 .../src}/deprecation/apply_deprecations.ts | 0 .../deprecation/deprecation_factory.test.ts | 0 .../src}/deprecation/deprecation_factory.ts | 0 packages/kbn-config/src/deprecation/index.ts | 28 ++++ .../kbn-config/src}/deprecation/types.ts | 0 .../kbn-config/src}/env.test.mocks.ts | 14 +- .../kbn-config/src}/env.test.ts | 47 ++++-- .../config => packages/kbn-config/src}/env.ts | 28 ++-- packages/kbn-config/src/index.ts | 37 +++++ ...gacy_object_to_config_adapter.test.ts.snap | 0 packages/kbn-config/src/legacy/index.ts | 23 +++ .../legacy_object_to_config_adapter.test.ts | 0 .../legacy_object_to_config_adapter.ts | 15 +- packages/kbn-config/src/mocks.ts | 28 ++++ .../src}/object_to_config_adapter.test.ts | 0 .../src}/object_to_config_adapter.ts | 0 .../__snapshots__/read_config.test.ts.snap | 0 .../src/raw}/ensure_deep_object.test.ts | 0 .../kbn-config/src/raw}/ensure_deep_object.ts | 0 packages/kbn-config/src/raw/index.ts | 21 +++ .../src/raw}/raw_config_service.mock.ts | 1 + .../src/raw}/raw_config_service.test.mocks.ts | 0 .../src/raw}/raw_config_service.test.ts | 2 +- .../kbn-config/src/raw}/raw_config_service.ts | 0 .../kbn-config/src/raw}/read_config.test.ts | 2 +- .../kbn-config/src/raw}/read_config.ts | 0 .../kbn-config/src}/types.ts | 0 packages/kbn-config/tsconfig.json | 12 ++ packages/kbn-config/yarn.lock | 1 + packages/kbn-logging/README.md | 61 +++++++ packages/kbn-logging/package.json | 18 +++ packages/kbn-logging/src/appenders.ts | 39 +++++ packages/kbn-logging/src/index.ts | 25 +++ packages/kbn-logging/src/layout.ts | 28 ++++ .../kbn-logging/src}/log_level.test.ts | 0 .../kbn-logging/src}/log_level.ts | 0 .../kbn-logging/src}/log_record.ts | 0 packages/kbn-logging/src/logger.ts | 96 +++++++++++ .../kbn-logging/src}/logger_factory.ts | 0 packages/kbn-logging/src/mocks/index.ts | 20 +++ packages/kbn-logging/src/mocks/logger.mock.ts | 70 ++++++++ packages/kbn-logging/tsconfig.json | 11 ++ packages/kbn-logging/yarn.lock | 1 + packages/kbn-std/package.json | 3 +- packages/kbn-std/src/index.ts | 2 +- packages/kbn-std/src/url.ts | 7 +- packages/kbn-std/tsconfig.json | 5 +- .../reload_logging_config.test.ts | 3 +- src/core/public/public.api.md | 29 +--- src/core/server/bootstrap.ts | 10 +- .../capabilities_service.test.ts | 6 +- .../deprecation/core_deprecations.test.ts | 3 +- .../config/deprecation/core_deprecations.ts | 2 +- src/core/server/config/deprecation/index.ts | 9 -- src/core/server/config/index.ts | 26 +-- src/core/server/config/mocks.ts | 25 +++ src/core/server/core_context.mock.ts | 7 +- .../elasticsearch_config.test.ts | 2 +- .../elasticsearch_service.test.ts | 6 +- .../environment/environment_service.test.ts | 3 +- .../http/cookie_session_storage.test.ts | 10 +- src/core/server/http/http_service.mock.ts | 2 +- src/core/server/http/http_service.test.ts | 11 +- .../lifecycle_handlers.test.ts | 2 +- src/core/server/http/test_utils.ts | 6 +- .../config/ensure_valid_configuration.test.ts | 2 +- src/core/server/legacy/config/index.ts | 1 - src/core/server/legacy/index.ts | 2 +- .../legacy/integration_tests/logging.test.ts | 4 +- .../server/legacy/legacy_internals.test.ts | 2 +- src/core/server/legacy/legacy_service.test.ts | 8 +- .../logging/appenders/legacy_appender.test.ts | 3 +- .../logging/appenders/legacy_appender.ts | 3 +- .../logging/legacy_logging_server.test.ts | 2 +- .../legacy/logging/legacy_logging_server.ts | 3 +- .../server/logging/appenders/appenders.ts | 21 +-- .../appenders/buffer/buffer_appender.test.ts | 3 +- .../appenders/buffer/buffer_appender.ts | 3 +- .../console/console_appender.test.ts | 3 +- .../appenders/console/console_appender.ts | 6 +- .../appenders/file/file_appender.test.ts | 3 +- .../logging/appenders/file/file_appender.ts | 5 +- src/core/server/logging/index.ts | 15 +- .../logging/layouts/conversions/date.ts | 2 +- .../logging/layouts/conversions/level.ts | 3 +- .../logging/layouts/conversions/logger.ts | 2 +- .../logging/layouts/conversions/message.ts | 2 +- .../logging/layouts/conversions/meta.ts | 3 +- .../server/logging/layouts/conversions/pid.ts | 2 +- .../logging/layouts/conversions/type.ts | 3 +- .../logging/layouts/json_layout.test.ts | 3 +- .../server/logging/layouts/json_layout.ts | 4 +- src/core/server/logging/layouts/layouts.ts | 10 +- .../logging/layouts/pattern_layout.test.ts | 3 +- .../server/logging/layouts/pattern_layout.ts | 3 +- src/core/server/logging/logger.mock.ts | 28 +--- src/core/server/logging/logger.test.ts | 3 +- src/core/server/logging/logger.ts | 81 +--------- src/core/server/logging/logger_adapter.ts | 3 +- src/core/server/logging/logging_service.ts | 2 +- .../server/logging/logging_system.mock.ts | 20 +-- src/core/server/logging/logging_system.ts | 8 +- .../server/metrics/metrics_service.test.ts | 3 +- src/core/server/mocks.ts | 2 +- .../plugin_manifest_parser.test.mocks.ts | 8 +- .../discovery/plugin_manifest_parser.test.ts | 4 +- .../discovery/plugins_discovery.test.mocks.ts | 9 +- .../discovery/plugins_discovery.test.ts | 8 +- .../plugins_service.test.mocks.ts | 12 +- .../integration_tests/plugins_service.test.ts | 7 +- src/core/server/plugins/plugin.test.ts | 6 +- .../server/plugins/plugin_context.test.ts | 6 +- .../server/plugins/plugins_config.test.ts | 7 +- .../plugins/plugins_service.test.mocks.ts | 24 ++- .../server/plugins/plugins_service.test.ts | 7 +- .../server/plugins/plugins_system.test.ts | 6 +- src/core/server/root/index.test.mocks.ts | 13 +- src/core/server/root/index.test.ts | 5 +- src/core/server/server.api.md | 152 ++++-------------- src/core/server/server.test.mocks.ts | 7 +- src/core/server/server.test.ts | 6 +- src/core/server/types.ts | 2 +- src/core/test_helpers/kbn_server.ts | 4 +- src/plugins/data/public/public.api.md | 4 + src/plugins/data/server/server.api.md | 6 + yarn.lock | 126 ++++++++++++++- 192 files changed, 1079 insertions(+), 1320 deletions(-) delete mode 100644 docs/development/core/public/kibana-plugin-core-public.environmentmode.dev.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.environmentmode.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.environmentmode.name.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.environmentmode.prod.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.packageinfo.branch.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.packageinfo.buildnum.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.packageinfo.buildsha.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.packageinfo.dist.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.packageinfo.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.packageinfo.version.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configdeprecation.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.rename.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.renamefromroot.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.unused.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.unusedfromroot.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configdeprecationlogger.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configdeprecationprovider.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.configpath.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.environmentmode.dev.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.environmentmode.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.environmentmode.name.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.environmentmode.prod.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logger.debug.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logger.error.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logger.fatal.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logger.get.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logger.info.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logger.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logger.trace.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logger.warn.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.loggerfactory.get.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.loggerfactory.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.logmeta.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.packageinfo.branch.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.packageinfo.buildnum.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.packageinfo.buildsha.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.packageinfo.dist.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.packageinfo.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.packageinfo.version.md create mode 100644 packages/kbn-config/README.md rename {src/core/server/config => packages/kbn-config}/__fixtures__/config.yml (100%) rename {src/core/server/config => packages/kbn-config}/__fixtures__/config_flat.yml (100%) rename {src/core/server/config => packages/kbn-config}/__fixtures__/en_var_ref_config.yml (100%) rename {src/core/server/config => packages/kbn-config}/__fixtures__/one.yml (100%) rename {src/core/server/config => packages/kbn-config}/__fixtures__/two.yml (100%) create mode 100644 packages/kbn-config/package.json rename {src/core/server/config => packages/kbn-config/src}/__mocks__/env.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/__snapshots__/config_service.test.ts.snap (100%) rename {src/core/server/config => packages/kbn-config/src}/__snapshots__/env.test.ts.snap (100%) rename {src/core/server/config => packages/kbn-config/src}/apply_argv.test.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/config.mock.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/config.test.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/config.ts (99%) rename {src/core/server/config => packages/kbn-config/src}/config_service.mock.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/config_service.test.mocks.ts (94%) rename {src/core/server/config => packages/kbn-config/src}/config_service.test.ts (95%) rename {src/core/server/config => packages/kbn-config/src}/config_service.ts (97%) rename {src/core/server/config => packages/kbn-config/src}/deprecation/apply_deprecations.test.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/deprecation/apply_deprecations.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/deprecation/deprecation_factory.test.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/deprecation/deprecation_factory.ts (100%) create mode 100644 packages/kbn-config/src/deprecation/index.ts rename {src/core/server/config => packages/kbn-config/src}/deprecation/types.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/env.test.mocks.ts (81%) rename {src/core/server/config => packages/kbn-config/src}/env.test.ts (89%) rename {src/core/server/config => packages/kbn-config/src}/env.ts (87%) create mode 100644 packages/kbn-config/src/index.ts rename {src/core/server/legacy/config => packages/kbn-config/src/legacy}/__snapshots__/legacy_object_to_config_adapter.test.ts.snap (100%) create mode 100644 packages/kbn-config/src/legacy/index.ts rename {src/core/server/legacy/config => packages/kbn-config/src/legacy}/legacy_object_to_config_adapter.test.ts (100%) rename {src/core/server/legacy/config => packages/kbn-config/src/legacy}/legacy_object_to_config_adapter.ts (92%) create mode 100644 packages/kbn-config/src/mocks.ts rename {src/core/server/config => packages/kbn-config/src}/object_to_config_adapter.test.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/object_to_config_adapter.ts (100%) rename {src/core/server/config => packages/kbn-config/src/raw}/__snapshots__/read_config.test.ts.snap (100%) rename {src/core/server/config => packages/kbn-config/src/raw}/ensure_deep_object.test.ts (100%) rename {src/core/server/config => packages/kbn-config/src/raw}/ensure_deep_object.ts (100%) create mode 100644 packages/kbn-config/src/raw/index.ts rename {src/core/server/config => packages/kbn-config/src/raw}/raw_config_service.mock.ts (99%) rename {src/core/server/config => packages/kbn-config/src/raw}/raw_config_service.test.mocks.ts (100%) rename {src/core/server/config => packages/kbn-config/src/raw}/raw_config_service.test.ts (98%) rename {src/core/server/config => packages/kbn-config/src/raw}/raw_config_service.ts (100%) rename {src/core/server/config => packages/kbn-config/src/raw}/read_config.test.ts (96%) rename {src/core/server/config => packages/kbn-config/src/raw}/read_config.ts (100%) rename {src/core/server/config => packages/kbn-config/src}/types.ts (100%) create mode 100644 packages/kbn-config/tsconfig.json create mode 120000 packages/kbn-config/yarn.lock create mode 100644 packages/kbn-logging/README.md create mode 100644 packages/kbn-logging/package.json create mode 100644 packages/kbn-logging/src/appenders.ts create mode 100644 packages/kbn-logging/src/index.ts create mode 100644 packages/kbn-logging/src/layout.ts rename {src/core/server/logging => packages/kbn-logging/src}/log_level.test.ts (100%) rename {src/core/server/logging => packages/kbn-logging/src}/log_level.ts (100%) rename {src/core/server/logging => packages/kbn-logging/src}/log_record.ts (100%) create mode 100644 packages/kbn-logging/src/logger.ts rename {src/core/server/logging => packages/kbn-logging/src}/logger_factory.ts (100%) create mode 100644 packages/kbn-logging/src/mocks/index.ts create mode 100644 packages/kbn-logging/src/mocks/logger.mock.ts create mode 100644 packages/kbn-logging/tsconfig.json create mode 120000 packages/kbn-logging/yarn.lock create mode 100644 src/core/server/config/mocks.ts diff --git a/docs/development/core/public/kibana-plugin-core-public.environmentmode.dev.md b/docs/development/core/public/kibana-plugin-core-public.environmentmode.dev.md deleted file mode 100644 index 68ea11c62cf96..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.environmentmode.dev.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [EnvironmentMode](./kibana-plugin-core-public.environmentmode.md) > [dev](./kibana-plugin-core-public.environmentmode.dev.md) - -## EnvironmentMode.dev property - -Signature: - -```typescript -dev: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.environmentmode.md b/docs/development/core/public/kibana-plugin-core-public.environmentmode.md deleted file mode 100644 index 34ca3a0000563..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.environmentmode.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [EnvironmentMode](./kibana-plugin-core-public.environmentmode.md) - -## EnvironmentMode interface - - -Signature: - -```typescript -export interface EnvironmentMode -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [dev](./kibana-plugin-core-public.environmentmode.dev.md) | boolean | | -| [name](./kibana-plugin-core-public.environmentmode.name.md) | 'development' | 'production' | | -| [prod](./kibana-plugin-core-public.environmentmode.prod.md) | boolean | | - diff --git a/docs/development/core/public/kibana-plugin-core-public.environmentmode.name.md b/docs/development/core/public/kibana-plugin-core-public.environmentmode.name.md deleted file mode 100644 index 2d1722a82417c..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.environmentmode.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [EnvironmentMode](./kibana-plugin-core-public.environmentmode.md) > [name](./kibana-plugin-core-public.environmentmode.name.md) - -## EnvironmentMode.name property - -Signature: - -```typescript -name: 'development' | 'production'; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.environmentmode.prod.md b/docs/development/core/public/kibana-plugin-core-public.environmentmode.prod.md deleted file mode 100644 index 5145f8f6887e5..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.environmentmode.prod.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [EnvironmentMode](./kibana-plugin-core-public.environmentmode.md) > [prod](./kibana-plugin-core-public.environmentmode.prod.md) - -## EnvironmentMode.prod property - -Signature: - -```typescript -prod: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index f2bf72a597656..85ef00d271415 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -55,7 +55,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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 | | [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 | | [FatalErrorsSetup](./kibana-plugin-core-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | @@ -85,7 +84,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [OverlayBannersStart](./kibana-plugin-core-public.overlaybannersstart.md) | | | [OverlayRef](./kibana-plugin-core-public.overlayref.md) | Returned by [OverlayStart](./kibana-plugin-core-public.overlaystart.md) methods for closing a mounted overlay. | | [OverlayStart](./kibana-plugin-core-public.overlaystart.md) | | -| [PackageInfo](./kibana-plugin-core-public.packageinfo.md) | | | [Plugin](./kibana-plugin-core-public.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginInitializerContext](./kibana-plugin-core-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | | [SavedObject](./kibana-plugin-core-public.savedobject.md) | | diff --git a/docs/development/core/public/kibana-plugin-core-public.packageinfo.branch.md b/docs/development/core/public/kibana-plugin-core-public.packageinfo.branch.md deleted file mode 100644 index 24fd8a74c84f1..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.packageinfo.branch.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PackageInfo](./kibana-plugin-core-public.packageinfo.md) > [branch](./kibana-plugin-core-public.packageinfo.branch.md) - -## PackageInfo.branch property - -Signature: - -```typescript -branch: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.packageinfo.buildnum.md b/docs/development/core/public/kibana-plugin-core-public.packageinfo.buildnum.md deleted file mode 100644 index b849b7bdc2a54..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.packageinfo.buildnum.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PackageInfo](./kibana-plugin-core-public.packageinfo.md) > [buildNum](./kibana-plugin-core-public.packageinfo.buildnum.md) - -## PackageInfo.buildNum property - -Signature: - -```typescript -buildNum: number; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.packageinfo.buildsha.md b/docs/development/core/public/kibana-plugin-core-public.packageinfo.buildsha.md deleted file mode 100644 index 42e5a21ab2f54..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.packageinfo.buildsha.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PackageInfo](./kibana-plugin-core-public.packageinfo.md) > [buildSha](./kibana-plugin-core-public.packageinfo.buildsha.md) - -## PackageInfo.buildSha property - -Signature: - -```typescript -buildSha: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.packageinfo.dist.md b/docs/development/core/public/kibana-plugin-core-public.packageinfo.dist.md deleted file mode 100644 index ac6e39e07172b..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.packageinfo.dist.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PackageInfo](./kibana-plugin-core-public.packageinfo.md) > [dist](./kibana-plugin-core-public.packageinfo.dist.md) - -## PackageInfo.dist property - -Signature: - -```typescript -dist: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.packageinfo.md b/docs/development/core/public/kibana-plugin-core-public.packageinfo.md deleted file mode 100644 index e819fb7990e5d..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.packageinfo.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PackageInfo](./kibana-plugin-core-public.packageinfo.md) - -## PackageInfo interface - - -Signature: - -```typescript -export interface PackageInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [branch](./kibana-plugin-core-public.packageinfo.branch.md) | string | | -| [buildNum](./kibana-plugin-core-public.packageinfo.buildnum.md) | number | | -| [buildSha](./kibana-plugin-core-public.packageinfo.buildsha.md) | string | | -| [dist](./kibana-plugin-core-public.packageinfo.dist.md) | boolean | | -| [version](./kibana-plugin-core-public.packageinfo.version.md) | string | | - diff --git a/docs/development/core/public/kibana-plugin-core-public.packageinfo.version.md b/docs/development/core/public/kibana-plugin-core-public.packageinfo.version.md deleted file mode 100644 index 5a7649a1445f6..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.packageinfo.version.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PackageInfo](./kibana-plugin-core-public.packageinfo.md) > [version](./kibana-plugin-core-public.packageinfo.version.md) - -## PackageInfo.version property - -Signature: - -```typescript -version: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.configdeprecation.md b/docs/development/core/server/kibana-plugin-core-server.configdeprecation.md deleted file mode 100644 index 6fa74bccb4fc5..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configdeprecation.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md) - -## ConfigDeprecation type - -Configuration deprecation returned from [ConfigDeprecationProvider](./kibana-plugin-core-server.configdeprecationprovider.md) that handles a single deprecation from the configuration. - -Signature: - -```typescript -export declare type ConfigDeprecation = (config: Record, fromPath: string, logger: ConfigDeprecationLogger) => Record; -``` - -## Remarks - -This should only be manually implemented if [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) does not provide the proper helpers for a specific deprecation need. - diff --git a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.md b/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.md deleted file mode 100644 index 8b34b379af67e..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.md +++ /dev/null @@ -1,36 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) - -## ConfigDeprecationFactory interface - -Provides helpers to generates the most commonly used [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md) when invoking a [ConfigDeprecationProvider](./kibana-plugin-core-server.configdeprecationprovider.md). - -See methods documentation for more detailed examples. - -Signature: - -```typescript -export interface ConfigDeprecationFactory -``` - -## Example - - -```typescript -const provider: ConfigDeprecationProvider = ({ rename, unused }) => [ - rename('oldKey', 'newKey'), - unused('deprecatedKey'), -] - -``` - -## Methods - -| Method | Description | -| --- | --- | -| [rename(oldKey, newKey)](./kibana-plugin-core-server.configdeprecationfactory.rename.md) | Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied. | -| [renameFromRoot(oldKey, newKey, silent)](./kibana-plugin-core-server.configdeprecationfactory.renamefromroot.md) | Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. | -| [unused(unusedKey)](./kibana-plugin-core-server.configdeprecationfactory.unused.md) | Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied. | -| [unusedFromRoot(unusedKey)](./kibana-plugin-core-server.configdeprecationfactory.unusedfromroot.md) | Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied.This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead. | - diff --git a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.rename.md b/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.rename.md deleted file mode 100644 index 91bf1b86fe52e..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.rename.md +++ /dev/null @@ -1,36 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) > [rename](./kibana-plugin-core-server.configdeprecationfactory.rename.md) - -## ConfigDeprecationFactory.rename() method - -Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied. - -Signature: - -```typescript -rename(oldKey: string, newKey: string): ConfigDeprecation; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| oldKey | string | | -| newKey | string | | - -Returns: - -`ConfigDeprecation` - -## Example - -Rename 'myplugin.oldKey' to 'myplugin.newKey' - -```typescript -const provider: ConfigDeprecationProvider = ({ rename }) => [ - rename('oldKey', 'newKey'), -] - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.renamefromroot.md b/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.renamefromroot.md deleted file mode 100644 index cc8cde9a5ed79..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.renamefromroot.md +++ /dev/null @@ -1,39 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) > [renameFromRoot](./kibana-plugin-core-server.configdeprecationfactory.renamefromroot.md) - -## ConfigDeprecationFactory.renameFromRoot() method - -Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied. - -This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. - -Signature: - -```typescript -renameFromRoot(oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| oldKey | string | | -| newKey | string | | -| silent | boolean | | - -Returns: - -`ConfigDeprecation` - -## Example - -Rename 'oldplugin.key' to 'newplugin.key' - -```typescript -const provider: ConfigDeprecationProvider = ({ renameFromRoot }) => [ - renameFromRoot('oldplugin.key', 'newplugin.key'), -] - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.unused.md b/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.unused.md deleted file mode 100644 index e7443b135984f..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.unused.md +++ /dev/null @@ -1,35 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) > [unused](./kibana-plugin-core-server.configdeprecationfactory.unused.md) - -## ConfigDeprecationFactory.unused() method - -Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied. - -Signature: - -```typescript -unused(unusedKey: string): ConfigDeprecation; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| unusedKey | string | | - -Returns: - -`ConfigDeprecation` - -## Example - -Flags 'myplugin.deprecatedKey' as unused - -```typescript -const provider: ConfigDeprecationProvider = ({ unused }) => [ - unused('deprecatedKey'), -] - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.unusedfromroot.md b/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.unusedfromroot.md deleted file mode 100644 index 8d5ed349e60e9..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configdeprecationfactory.unusedfromroot.md +++ /dev/null @@ -1,37 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) > [unusedFromRoot](./kibana-plugin-core-server.configdeprecationfactory.unusedfromroot.md) - -## ConfigDeprecationFactory.unusedFromRoot() method - -Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied. - -This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead. - -Signature: - -```typescript -unusedFromRoot(unusedKey: string): ConfigDeprecation; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| unusedKey | string | | - -Returns: - -`ConfigDeprecation` - -## Example - -Flags 'somepath.deprecatedProperty' as unused - -```typescript -const provider: ConfigDeprecationProvider = ({ unusedFromRoot }) => [ - unusedFromRoot('somepath.deprecatedProperty'), -] - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.configdeprecationlogger.md b/docs/development/core/server/kibana-plugin-core-server.configdeprecationlogger.md deleted file mode 100644 index 15da7d24b4a7c..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configdeprecationlogger.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigDeprecationLogger](./kibana-plugin-core-server.configdeprecationlogger.md) - -## ConfigDeprecationLogger type - -Logger interface used when invoking a [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md) - -Signature: - -```typescript -export declare type ConfigDeprecationLogger = (message: string) => void; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.configdeprecationprovider.md b/docs/development/core/server/kibana-plugin-core-server.configdeprecationprovider.md deleted file mode 100644 index 64351cbdb4a71..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configdeprecationprovider.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigDeprecationProvider](./kibana-plugin-core-server.configdeprecationprovider.md) - -## ConfigDeprecationProvider type - -A provider that should returns a list of [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md). - -See [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) for more usage examples. - -Signature: - -```typescript -export declare type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => ConfigDeprecation[]; -``` - -## Example - - -```typescript -const provider: ConfigDeprecationProvider = ({ rename, unused }) => [ - rename('oldKey', 'newKey'), - unused('deprecatedKey'), - myCustomDeprecation, -] - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.configpath.md b/docs/development/core/server/kibana-plugin-core-server.configpath.md deleted file mode 100644 index 0b15d3ca727f2..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.configpath.md +++ /dev/null @@ -1,12 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ConfigPath](./kibana-plugin-core-server.configpath.md) - -## ConfigPath type - - -Signature: - -```typescript -export declare type ConfigPath = string | string[]; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.environmentmode.dev.md b/docs/development/core/server/kibana-plugin-core-server.environmentmode.dev.md deleted file mode 100644 index c1b9d5b7e19b1..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.environmentmode.dev.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) > [dev](./kibana-plugin-core-server.environmentmode.dev.md) - -## EnvironmentMode.dev property - -Signature: - -```typescript -dev: boolean; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.environmentmode.md b/docs/development/core/server/kibana-plugin-core-server.environmentmode.md deleted file mode 100644 index 94795754739ab..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.environmentmode.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) - -## EnvironmentMode interface - - -Signature: - -```typescript -export interface EnvironmentMode -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [dev](./kibana-plugin-core-server.environmentmode.dev.md) | boolean | | -| [name](./kibana-plugin-core-server.environmentmode.name.md) | 'development' | 'production' | | -| [prod](./kibana-plugin-core-server.environmentmode.prod.md) | boolean | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.environmentmode.name.md b/docs/development/core/server/kibana-plugin-core-server.environmentmode.name.md deleted file mode 100644 index 5627deab9e83e..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.environmentmode.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) > [name](./kibana-plugin-core-server.environmentmode.name.md) - -## EnvironmentMode.name property - -Signature: - -```typescript -name: 'development' | 'production'; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.environmentmode.prod.md b/docs/development/core/server/kibana-plugin-core-server.environmentmode.prod.md deleted file mode 100644 index 1ed3fa33b8997..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.environmentmode.prod.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) > [prod](./kibana-plugin-core-server.environmentmode.prod.md) - -## EnvironmentMode.prod property - -Signature: - -```typescript -prod: boolean; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.logger.debug.md b/docs/development/core/server/kibana-plugin-core-server.logger.debug.md deleted file mode 100644 index c2b95d92b21d1..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logger.debug.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Logger](./kibana-plugin-core-server.logger.md) > [debug](./kibana-plugin-core-server.logger.debug.md) - -## Logger.debug() method - -Log messages useful for debugging and interactive investigation - -Signature: - -```typescript -debug(message: string, meta?: LogMeta): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| message | string | The log message | -| meta | LogMeta | | - -Returns: - -`void` - diff --git a/docs/development/core/server/kibana-plugin-core-server.logger.error.md b/docs/development/core/server/kibana-plugin-core-server.logger.error.md deleted file mode 100644 index 68e7499fabc32..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logger.error.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Logger](./kibana-plugin-core-server.logger.md) > [error](./kibana-plugin-core-server.logger.error.md) - -## Logger.error() method - -Logs abnormal or unexpected errors or messages that caused a failure in the application flow - -Signature: - -```typescript -error(errorOrMessage: string | Error, meta?: LogMeta): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| errorOrMessage | string | Error | An Error object or message string to log | -| meta | LogMeta | | - -Returns: - -`void` - diff --git a/docs/development/core/server/kibana-plugin-core-server.logger.fatal.md b/docs/development/core/server/kibana-plugin-core-server.logger.fatal.md deleted file mode 100644 index d39194d2126b7..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logger.fatal.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Logger](./kibana-plugin-core-server.logger.md) > [fatal](./kibana-plugin-core-server.logger.fatal.md) - -## Logger.fatal() method - -Logs abnormal or unexpected errors or messages that caused an unrecoverable failure - -Signature: - -```typescript -fatal(errorOrMessage: string | Error, meta?: LogMeta): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| errorOrMessage | string | Error | An Error object or message string to log | -| meta | LogMeta | | - -Returns: - -`void` - diff --git a/docs/development/core/server/kibana-plugin-core-server.logger.get.md b/docs/development/core/server/kibana-plugin-core-server.logger.get.md deleted file mode 100644 index 5f69831a73808..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logger.get.md +++ /dev/null @@ -1,33 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Logger](./kibana-plugin-core-server.logger.md) > [get](./kibana-plugin-core-server.logger.get.md) - -## Logger.get() method - -Returns a new [Logger](./kibana-plugin-core-server.logger.md) instance extending the current logger context. - -Signature: - -```typescript -get(...childContextPaths: string[]): Logger; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| childContextPaths | string[] | | - -Returns: - -`Logger` - -## Example - - -```typescript -const logger = loggerFactory.get('plugin', 'service'); // 'plugin.service' context -const subLogger = logger.get('feature'); // 'plugin.service.feature' context - -``` - diff --git a/docs/development/core/server/kibana-plugin-core-server.logger.info.md b/docs/development/core/server/kibana-plugin-core-server.logger.info.md deleted file mode 100644 index 7e786ed0e3f9b..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logger.info.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Logger](./kibana-plugin-core-server.logger.md) > [info](./kibana-plugin-core-server.logger.info.md) - -## Logger.info() method - -Logs messages related to general application flow - -Signature: - -```typescript -info(message: string, meta?: LogMeta): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| message | string | The log message | -| meta | LogMeta | | - -Returns: - -`void` - diff --git a/docs/development/core/server/kibana-plugin-core-server.logger.md b/docs/development/core/server/kibana-plugin-core-server.logger.md deleted file mode 100644 index 7012358524c35..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logger.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Logger](./kibana-plugin-core-server.logger.md) - -## Logger interface - -Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. - -Signature: - -```typescript -export interface Logger -``` - -## Methods - -| Method | Description | -| --- | --- | -| [debug(message, meta)](./kibana-plugin-core-server.logger.debug.md) | Log messages useful for debugging and interactive investigation | -| [error(errorOrMessage, meta)](./kibana-plugin-core-server.logger.error.md) | Logs abnormal or unexpected errors or messages that caused a failure in the application flow | -| [fatal(errorOrMessage, meta)](./kibana-plugin-core-server.logger.fatal.md) | Logs abnormal or unexpected errors or messages that caused an unrecoverable failure | -| [get(childContextPaths)](./kibana-plugin-core-server.logger.get.md) | Returns a new [Logger](./kibana-plugin-core-server.logger.md) instance extending the current logger context. | -| [info(message, meta)](./kibana-plugin-core-server.logger.info.md) | Logs messages related to general application flow | -| [trace(message, meta)](./kibana-plugin-core-server.logger.trace.md) | Log messages at the most detailed log level | -| [warn(errorOrMessage, meta)](./kibana-plugin-core-server.logger.warn.md) | Logs abnormal or unexpected errors or messages | - diff --git a/docs/development/core/server/kibana-plugin-core-server.logger.trace.md b/docs/development/core/server/kibana-plugin-core-server.logger.trace.md deleted file mode 100644 index b8d761f69541e..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logger.trace.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Logger](./kibana-plugin-core-server.logger.md) > [trace](./kibana-plugin-core-server.logger.trace.md) - -## Logger.trace() method - -Log messages at the most detailed log level - -Signature: - -```typescript -trace(message: string, meta?: LogMeta): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| message | string | The log message | -| meta | LogMeta | | - -Returns: - -`void` - diff --git a/docs/development/core/server/kibana-plugin-core-server.logger.warn.md b/docs/development/core/server/kibana-plugin-core-server.logger.warn.md deleted file mode 100644 index 21cefe4f748dd..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logger.warn.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Logger](./kibana-plugin-core-server.logger.md) > [warn](./kibana-plugin-core-server.logger.warn.md) - -## Logger.warn() method - -Logs abnormal or unexpected errors or messages - -Signature: - -```typescript -warn(errorOrMessage: string | Error, meta?: LogMeta): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| errorOrMessage | string | Error | An Error object or message string to log | -| meta | LogMeta | | - -Returns: - -`void` - diff --git a/docs/development/core/server/kibana-plugin-core-server.loggerfactory.get.md b/docs/development/core/server/kibana-plugin-core-server.loggerfactory.get.md deleted file mode 100644 index d081d359b9480..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.loggerfactory.get.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LoggerFactory](./kibana-plugin-core-server.loggerfactory.md) > [get](./kibana-plugin-core-server.loggerfactory.get.md) - -## LoggerFactory.get() method - -Returns a `Logger` instance for the specified context. - -Signature: - -```typescript -get(...contextParts: string[]): Logger; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| contextParts | string[] | Parts of the context to return logger for. For example get('plugins', 'pid') will return a logger for the plugins.pid context. | - -Returns: - -`Logger` - diff --git a/docs/development/core/server/kibana-plugin-core-server.loggerfactory.md b/docs/development/core/server/kibana-plugin-core-server.loggerfactory.md deleted file mode 100644 index 8795413bf675a..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.loggerfactory.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LoggerFactory](./kibana-plugin-core-server.loggerfactory.md) - -## LoggerFactory interface - -The single purpose of `LoggerFactory` interface is to define a way to retrieve a context-based logger instance. - -Signature: - -```typescript -export interface LoggerFactory -``` - -## Methods - -| Method | Description | -| --- | --- | -| [get(contextParts)](./kibana-plugin-core-server.loggerfactory.get.md) | Returns a Logger instance for the specified context. | - diff --git a/docs/development/core/server/kibana-plugin-core-server.logmeta.md b/docs/development/core/server/kibana-plugin-core-server.logmeta.md deleted file mode 100644 index 56a2af7e826c6..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.logmeta.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LogMeta](./kibana-plugin-core-server.logmeta.md) - -## LogMeta interface - -Contextual metadata - -Signature: - -```typescript -export interface LogMeta -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 30b98b9f0553e..69f2cf0338a01 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -65,7 +65,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [Capabilities](./kibana-plugin-core-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | | [CapabilitiesSetup](./kibana-plugin-core-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-core-server.capabilities.md) that will be used by the application.Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the registerProvider method.Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the registerSwitcher method.Refers to the methods documentation for complete description and examples. | | [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-core-server.capabilities.md). | -| [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) | Provides helpers to generates the most commonly used [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md) when invoking a [ConfigDeprecationProvider](./kibana-plugin-core-server.configdeprecationprovider.md).See methods documentation for more detailed examples. | | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-core-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-core-server.corestart.md) | Context passed to the plugins start method. | @@ -81,7 +80,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | | [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | -| [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) | | | [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters | | [Explanation](./kibana-plugin-core-server.explanation.md) | | | [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | @@ -114,11 +112,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LegacyRequest](./kibana-plugin-core-server.legacyrequest.md) | | | [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) | | | [OnPostAuthToolkit](./kibana-plugin-core-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | @@ -131,7 +126,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [OpsOsMetrics](./kibana-plugin-core-server.opsosmetrics.md) | OS related metrics | | [OpsProcessMetrics](./kibana-plugin-core-server.opsprocessmetrics.md) | Process related metrics | | [OpsServerMetrics](./kibana-plugin-core-server.opsservermetrics.md) | server related metrics | -| [PackageInfo](./kibana-plugin-core-server.packageinfo.md) | | | [Plugin](./kibana-plugin-core-server.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginConfigDescriptor](./kibana-plugin-core-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. | | [PluginInitializerContext](./kibana-plugin-core-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | @@ -233,10 +227,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthResult](./kibana-plugin-core-server.authresult.md) | | | [CapabilitiesProvider](./kibana-plugin-core-server.capabilitiesprovider.md) | See [CapabilitiesSetup](./kibana-plugin-core-server.capabilitiessetup.md) | | [CapabilitiesSwitcher](./kibana-plugin-core-server.capabilitiesswitcher.md) | See [CapabilitiesSetup](./kibana-plugin-core-server.capabilitiessetup.md) | -| [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md) | Configuration deprecation returned from [ConfigDeprecationProvider](./kibana-plugin-core-server.configdeprecationprovider.md) that handles a single deprecation from the configuration. | -| [ConfigDeprecationLogger](./kibana-plugin-core-server.configdeprecationlogger.md) | Logger interface used when invoking a [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md) | -| [ConfigDeprecationProvider](./kibana-plugin-core-server.configdeprecationprovider.md) | A provider that should returns a list of [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md).See [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) for more usage examples. | -| [ConfigPath](./kibana-plugin-core-server.configpath.md) | | | [DestructiveRouteMethod](./kibana-plugin-core-server.destructiveroutemethod.md) | Set of HTTP methods changing the state of the server. | | [ElasticsearchClient](./kibana-plugin-core-server.elasticsearchclient.md) | Client used to query the elasticsearch cluster. | | [ElasticsearchClientConfig](./kibana-plugin-core-server.elasticsearchclientconfig.md) | Configuration options to be used to create a [cluster client](./kibana-plugin-core-server.iclusterclient.md) using the [createClient API](./kibana-plugin-core-server.elasticsearchservicestart.createclient.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.packageinfo.branch.md b/docs/development/core/server/kibana-plugin-core-server.packageinfo.branch.md deleted file mode 100644 index 34fbd56045921..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.packageinfo.branch.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PackageInfo](./kibana-plugin-core-server.packageinfo.md) > [branch](./kibana-plugin-core-server.packageinfo.branch.md) - -## PackageInfo.branch property - -Signature: - -```typescript -branch: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.packageinfo.buildnum.md b/docs/development/core/server/kibana-plugin-core-server.packageinfo.buildnum.md deleted file mode 100644 index acc33fae18fbc..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.packageinfo.buildnum.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PackageInfo](./kibana-plugin-core-server.packageinfo.md) > [buildNum](./kibana-plugin-core-server.packageinfo.buildnum.md) - -## PackageInfo.buildNum property - -Signature: - -```typescript -buildNum: number; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.packageinfo.buildsha.md b/docs/development/core/server/kibana-plugin-core-server.packageinfo.buildsha.md deleted file mode 100644 index 14ad75168fd85..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.packageinfo.buildsha.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PackageInfo](./kibana-plugin-core-server.packageinfo.md) > [buildSha](./kibana-plugin-core-server.packageinfo.buildsha.md) - -## PackageInfo.buildSha property - -Signature: - -```typescript -buildSha: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.packageinfo.dist.md b/docs/development/core/server/kibana-plugin-core-server.packageinfo.dist.md deleted file mode 100644 index d7567010ddd53..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.packageinfo.dist.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PackageInfo](./kibana-plugin-core-server.packageinfo.md) > [dist](./kibana-plugin-core-server.packageinfo.dist.md) - -## PackageInfo.dist property - -Signature: - -```typescript -dist: boolean; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.packageinfo.md b/docs/development/core/server/kibana-plugin-core-server.packageinfo.md deleted file mode 100644 index 3e1789d8df896..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.packageinfo.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PackageInfo](./kibana-plugin-core-server.packageinfo.md) - -## PackageInfo interface - - -Signature: - -```typescript -export interface PackageInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [branch](./kibana-plugin-core-server.packageinfo.branch.md) | string | | -| [buildNum](./kibana-plugin-core-server.packageinfo.buildnum.md) | number | | -| [buildSha](./kibana-plugin-core-server.packageinfo.buildsha.md) | string | | -| [dist](./kibana-plugin-core-server.packageinfo.dist.md) | boolean | | -| [version](./kibana-plugin-core-server.packageinfo.version.md) | string | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.packageinfo.version.md b/docs/development/core/server/kibana-plugin-core-server.packageinfo.version.md deleted file mode 100644 index 1606ab5901941..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.packageinfo.version.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PackageInfo](./kibana-plugin-core-server.packageinfo.md) > [version](./kibana-plugin-core-server.packageinfo.version.md) - -## PackageInfo.version property - -Signature: - -```typescript -version: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.deprecations.md b/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.deprecations.md index d539b5a0501c1..d06b3cb9fa64a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.deprecations.md +++ b/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.deprecations.md @@ -4,7 +4,7 @@ ## PluginConfigDescriptor.deprecations property -Provider for the [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md) to apply to the plugin configuration. +Provider for the to apply to the plugin configuration. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.md b/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.md index 964d45f155948..5708c4f9a3f88 100644 --- a/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.md +++ b/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.md @@ -44,7 +44,7 @@ export const config: PluginConfigDescriptor = { | Property | Type | Description | | --- | --- | --- | -| [deprecations](./kibana-plugin-core-server.pluginconfigdescriptor.deprecations.md) | ConfigDeprecationProvider | Provider for the [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md) to apply to the plugin configuration. | +| [deprecations](./kibana-plugin-core-server.pluginconfigdescriptor.deprecations.md) | ConfigDeprecationProvider | Provider for the to apply to the plugin configuration. | | [exposeToBrowser](./kibana-plugin-core-server.pluginconfigdescriptor.exposetobrowser.md) | {
[P in keyof T]?: boolean;
} | List of configuration properties that will be available on the client-side plugin. | | [schema](./kibana-plugin-core-server.pluginconfigdescriptor.schema.md) | PluginConfigSchema<T> | Schema to use to validate the plugin configuration.[PluginConfigSchema](./kibana-plugin-core-server.pluginconfigschema.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.configpath.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.configpath.md index c391431e12b1c..8aa603242df8e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.configpath.md +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.configpath.md @@ -4,7 +4,7 @@ ## PluginManifest.configPath property -Root [configuration path](./kibana-plugin-core-server.configpath.md) used by the plugin, defaults to "id" in snake\_case format. +Root used by the plugin, defaults to "id" in snake\_case format. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md index 6db2f89590149..b0182a7c48e16 100644 --- a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md @@ -20,7 +20,7 @@ Should never be used in code outside of Core but is exported for documentation p | Property | Type | Description | | --- | --- | --- | -| [configPath](./kibana-plugin-core-server.pluginmanifest.configpath.md) | ConfigPath | Root [configuration path](./kibana-plugin-core-server.configpath.md) used by the plugin, defaults to "id" in snake\_case format. | +| [configPath](./kibana-plugin-core-server.pluginmanifest.configpath.md) | ConfigPath | Root used by the plugin, defaults to "id" in snake\_case format. | | [extraPublicDirs](./kibana-plugin-core-server.pluginmanifest.extrapublicdirs.md) | string[] | Specifies directory names that can be imported by other ui-plugins built using the same instance of the @kbn/optimizer. A temporary measure we plan to replace with better mechanisms for sharing static code between plugins | | [id](./kibana-plugin-core-server.pluginmanifest.id.md) | PluginName | Identifier of the plugin. Must be a string in camelCase. Part of a plugin public contract. Other plugins leverage it to access plugin API, navigate to the plugin, etc. | | [kibanaVersion](./kibana-plugin-core-server.pluginmanifest.kibanaversion.md) | string | The version of Kibana the plugin is compatible with, defaults to "version". | diff --git a/package.json b/package.json index 8994f327d3e65..5008bc0bcebc5 100644 --- a/package.json +++ b/package.json @@ -137,9 +137,11 @@ "@hapi/wreck": "^15.0.2", "@kbn/analytics": "1.0.0", "@kbn/babel-preset": "1.0.0", + "@kbn/config": "1.0.0", "@kbn/config-schema": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", + "@kbn/logging": "1.0.0", "@kbn/pm": "1.0.0", "@kbn/std": "1.0.0", "@kbn/telemetry-tools": "1.0.0", diff --git a/packages/kbn-config/README.md b/packages/kbn-config/README.md new file mode 100644 index 0000000000000..343748cb0d817 --- /dev/null +++ b/packages/kbn-config/README.md @@ -0,0 +1,3 @@ +# `@kbn/config` — Kibana configuration file loader + +This package contains the configuration service used to load and read the kibana configuration file diff --git a/src/core/server/config/__fixtures__/config.yml b/packages/kbn-config/__fixtures__/config.yml similarity index 100% rename from src/core/server/config/__fixtures__/config.yml rename to packages/kbn-config/__fixtures__/config.yml diff --git a/src/core/server/config/__fixtures__/config_flat.yml b/packages/kbn-config/__fixtures__/config_flat.yml similarity index 100% rename from src/core/server/config/__fixtures__/config_flat.yml rename to packages/kbn-config/__fixtures__/config_flat.yml diff --git a/src/core/server/config/__fixtures__/en_var_ref_config.yml b/packages/kbn-config/__fixtures__/en_var_ref_config.yml similarity index 100% rename from src/core/server/config/__fixtures__/en_var_ref_config.yml rename to packages/kbn-config/__fixtures__/en_var_ref_config.yml diff --git a/src/core/server/config/__fixtures__/one.yml b/packages/kbn-config/__fixtures__/one.yml similarity index 100% rename from src/core/server/config/__fixtures__/one.yml rename to packages/kbn-config/__fixtures__/one.yml diff --git a/src/core/server/config/__fixtures__/two.yml b/packages/kbn-config/__fixtures__/two.yml similarity index 100% rename from src/core/server/config/__fixtures__/two.yml rename to packages/kbn-config/__fixtures__/two.yml diff --git a/packages/kbn-config/package.json b/packages/kbn-config/package.json new file mode 100644 index 0000000000000..2d9dbc3b7ab8f --- /dev/null +++ b/packages/kbn-config/package.json @@ -0,0 +1,30 @@ +{ + "name": "@kbn/config", + "main": "./target/index.js", + "types": "./target/index.d.ts", + "version": "1.0.0", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "tsc", + "kbn:bootstrap": "yarn build" + }, + "dependencies": { + "@elastic/safer-lodash-set": "0.0.0", + "@kbn/config-schema": "1.0.0", + "@kbn/dev-utils": "1.0.0", + "@kbn/logging": "1.0.0", + "@kbn/std": "1.0.0", + "@kbn/utility-types": "1.0.0", + "js-yaml": "3.13.1", + "load-json-file": "^6.2.0", + "lodash": "^4.17.20", + "moment": "^2.24.0", + "rxjs": "^6.5.5", + "type-detect": "^4.0.8" + }, + "devDependencies": { + "typescript": "4.0.2", + "tsd": "^0.7.4" + } +} diff --git a/src/core/server/config/__mocks__/env.ts b/packages/kbn-config/src/__mocks__/env.ts similarity index 100% rename from src/core/server/config/__mocks__/env.ts rename to packages/kbn-config/src/__mocks__/env.ts diff --git a/src/core/server/config/__snapshots__/config_service.test.ts.snap b/packages/kbn-config/src/__snapshots__/config_service.test.ts.snap similarity index 100% rename from src/core/server/config/__snapshots__/config_service.test.ts.snap rename to packages/kbn-config/src/__snapshots__/config_service.test.ts.snap diff --git a/src/core/server/config/__snapshots__/env.test.ts.snap b/packages/kbn-config/src/__snapshots__/env.test.ts.snap similarity index 100% rename from src/core/server/config/__snapshots__/env.test.ts.snap rename to packages/kbn-config/src/__snapshots__/env.test.ts.snap diff --git a/src/core/server/config/apply_argv.test.ts b/packages/kbn-config/src/apply_argv.test.ts similarity index 100% rename from src/core/server/config/apply_argv.test.ts rename to packages/kbn-config/src/apply_argv.test.ts diff --git a/src/core/server/config/config.mock.ts b/packages/kbn-config/src/config.mock.ts similarity index 100% rename from src/core/server/config/config.mock.ts rename to packages/kbn-config/src/config.mock.ts diff --git a/src/core/server/config/config.test.ts b/packages/kbn-config/src/config.test.ts similarity index 100% rename from src/core/server/config/config.test.ts rename to packages/kbn-config/src/config.test.ts diff --git a/src/core/server/config/config.ts b/packages/kbn-config/src/config.ts similarity index 99% rename from src/core/server/config/config.ts rename to packages/kbn-config/src/config.ts index a4026b1d88ac3..3d012dd5f3885 100644 --- a/src/core/server/config/config.ts +++ b/packages/kbn-config/src/config.ts @@ -23,7 +23,7 @@ export type ConfigPath = string | string[]; /** * Checks whether specified value can be considered as config path. * @param value Value to check. - * @internal + * @public */ export function isConfigPath(value: unknown): value is ConfigPath { if (!value) { diff --git a/src/core/server/config/config_service.mock.ts b/packages/kbn-config/src/config_service.mock.ts similarity index 100% rename from src/core/server/config/config_service.mock.ts rename to packages/kbn-config/src/config_service.mock.ts diff --git a/src/core/server/config/config_service.test.mocks.ts b/packages/kbn-config/src/config_service.test.mocks.ts similarity index 94% rename from src/core/server/config/config_service.test.mocks.ts rename to packages/kbn-config/src/config_service.test.mocks.ts index 1299c4c0b4eb1..1b70802e9805e 100644 --- a/src/core/server/config/config_service.test.mocks.ts +++ b/packages/kbn-config/src/config_service.test.mocks.ts @@ -18,7 +18,7 @@ */ export const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] }); -jest.mock('../../../../package.json', () => mockPackage); +jest.mock('../../../package.json', () => mockPackage); export const mockApplyDeprecations = jest.fn((config, deprecations, log) => config); jest.mock('./deprecation/apply_deprecations', () => ({ diff --git a/src/core/server/config/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts similarity index 95% rename from src/core/server/config/config_service.test.ts rename to packages/kbn-config/src/config_service.test.ts index 95153447bd4a9..b32b405455700 100644 --- a/src/core/server/config/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -20,22 +20,36 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { first, take } from 'rxjs/operators'; -import { mockPackage, mockApplyDeprecations } from './config_service.test.mocks'; -import { rawConfigServiceMock } from './raw_config_service.mock'; +import { mockApplyDeprecations } from './config_service.test.mocks'; +import { rawConfigServiceMock } from './raw/raw_config_service.mock'; import { schema } from '@kbn/config-schema'; +import { MockedLogger, loggerMock } from '@kbn/logging/target/mocks'; + +import { ConfigService, Env, RawPackageInfo } from '.'; -import { ConfigService, Env } from '.'; -import { loggingSystemMock } from '../logging/logging_system.mock'; import { getEnvOptions } from './__mocks__/env'; +const packageInfos: RawPackageInfo = { + branch: 'master', + version: '8.0.0', + build: { + number: 42, + sha: 'one', + }, +}; const emptyArgv = getEnvOptions(); -const defaultEnv = new Env('/kibana', emptyArgv); -const logger = loggingSystemMock.create(); +const defaultEnv = new Env('/kibana', packageInfos, emptyArgv); + +let logger: MockedLogger; const getRawConfigProvider = (rawConfig: Record) => rawConfigServiceMock.create({ rawConfig }); +beforeEach(() => { + logger = loggerMock.create(); +}); + test('returns config at path as observable', async () => { const rawConfig = getRawConfigProvider({ key: 'foo' }); const configService = new ConfigService(rawConfig, defaultEnv, logger); @@ -237,7 +251,7 @@ test('tracks unhandled paths', async () => { }); test('correctly passes context', async () => { - mockPackage.raw = { + const mockPackage = { branch: 'feature-v1', version: 'v1', build: { @@ -247,7 +261,7 @@ test('correctly passes context', async () => { }, }; - const env = new Env('/kibana', getEnvOptions()); + const env = new Env('/kibana', mockPackage, getEnvOptions()); const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: { foo: {} } }); const schemaDefinition = schema.object({ @@ -441,9 +455,9 @@ test('logs deprecation warning during validation', async () => { return config; }); - loggingSystemMock.clear(logger); + loggerMock.clear(logger); await configService.validate(); - expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` + expect(loggerMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ "some deprecation message", diff --git a/src/core/server/config/config_service.ts b/packages/kbn-config/src/config_service.ts similarity index 97% rename from src/core/server/config/config_service.ts rename to packages/kbn-config/src/config_service.ts index d77ee980b0491..faa8397cd6d6e 100644 --- a/src/core/server/config/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -16,23 +16,24 @@ * specific language governing permissions and limitations * under the License. */ + import type { PublicMethodsOf } from '@kbn/utility-types'; import { Type } from '@kbn/config-schema'; import { isEqual } from 'lodash'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { distinctUntilChanged, first, map, shareReplay, take } from 'rxjs/operators'; +import { Logger, LoggerFactory } from '@kbn/logging'; import { Config, ConfigPath, Env } from '.'; -import { Logger, LoggerFactory } from '../logging'; import { hasConfigPathIntersection } from './config'; -import { RawConfigurationProvider } from './raw_config_service'; +import { RawConfigurationProvider } from './raw/raw_config_service'; import { applyDeprecations, ConfigDeprecationWithContext, ConfigDeprecationProvider, configDeprecationFactory, } from './deprecation'; -import { LegacyObjectToConfigAdapter } from '../legacy/config'; +import { LegacyObjectToConfigAdapter } from './legacy'; /** @internal */ export type IConfigService = PublicMethodsOf; diff --git a/src/core/server/config/deprecation/apply_deprecations.test.ts b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts similarity index 100% rename from src/core/server/config/deprecation/apply_deprecations.test.ts rename to packages/kbn-config/src/deprecation/apply_deprecations.test.ts diff --git a/src/core/server/config/deprecation/apply_deprecations.ts b/packages/kbn-config/src/deprecation/apply_deprecations.ts similarity index 100% rename from src/core/server/config/deprecation/apply_deprecations.ts rename to packages/kbn-config/src/deprecation/apply_deprecations.ts diff --git a/src/core/server/config/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts similarity index 100% rename from src/core/server/config/deprecation/deprecation_factory.test.ts rename to packages/kbn-config/src/deprecation/deprecation_factory.test.ts diff --git a/src/core/server/config/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts similarity index 100% rename from src/core/server/config/deprecation/deprecation_factory.ts rename to packages/kbn-config/src/deprecation/deprecation_factory.ts diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts new file mode 100644 index 0000000000000..504dbfeeb001a --- /dev/null +++ b/packages/kbn-config/src/deprecation/index.ts @@ -0,0 +1,28 @@ +/* + * 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 { + ConfigDeprecation, + ConfigDeprecationWithContext, + ConfigDeprecationLogger, + ConfigDeprecationFactory, + ConfigDeprecationProvider, +} from './types'; +export { configDeprecationFactory } from './deprecation_factory'; +export { applyDeprecations } from './apply_deprecations'; diff --git a/src/core/server/config/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts similarity index 100% rename from src/core/server/config/deprecation/types.ts rename to packages/kbn-config/src/deprecation/types.ts diff --git a/src/core/server/config/env.test.mocks.ts b/packages/kbn-config/src/env.test.mocks.ts similarity index 81% rename from src/core/server/config/env.test.mocks.ts rename to packages/kbn-config/src/env.test.mocks.ts index d35afdf0b66e5..3976b1c2016fb 100644 --- a/src/core/server/config/env.test.mocks.ts +++ b/packages/kbn-config/src/env.test.mocks.ts @@ -17,7 +17,10 @@ * under the License. */ -jest.mock('path', () => ({ +const realPath = jest.requireActual('path'); + +jest.doMock('path', () => ({ + ...realPath, resolve(...pathSegments: string[]) { return pathSegments.join('/'); }, @@ -26,5 +29,10 @@ jest.mock('path', () => ({ }, })); -export const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] }); -jest.mock('../../../../package.json', () => mockPackage); +export const mockPackage = { + raw: {}, +}; + +jest.doMock('load-json-file', () => ({ + sync: () => mockPackage.raw, +})); diff --git a/src/core/server/config/env.test.ts b/packages/kbn-config/src/env.test.ts similarity index 89% rename from src/core/server/config/env.test.ts rename to packages/kbn-config/src/env.test.ts index 0fffcc44781d9..f3d51a021246e 100644 --- a/src/core/server/config/env.test.ts +++ b/packages/kbn-config/src/env.test.ts @@ -19,9 +19,24 @@ import { mockPackage } from './env.test.mocks'; -import { Env } from '.'; +import { Env, RawPackageInfo } from './env'; import { getEnvOptions } from './__mocks__/env'; +const REPO_ROOT = '/test/kibanaRoot'; + +const packageInfos: RawPackageInfo = { + branch: 'master', + version: '8.0.0', + build: { + number: 42, + sha: 'one', + }, +}; + +beforeEach(() => { + mockPackage.raw = {}; +}); + test('correctly creates default environment in dev mode.', () => { mockPackage.raw = { branch: 'some-branch', @@ -29,6 +44,7 @@ test('correctly creates default environment in dev mode.', () => { }; const defaultEnv = Env.createDefault( + REPO_ROOT, getEnvOptions({ configs: ['/test/cwd/config/kibana.yml'], isDevClusterMaster: true, @@ -50,6 +66,7 @@ test('correctly creates default environment in prod distributable mode.', () => }; const defaultEnv = Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { dev: false }, configs: ['/some/other/path/some-kibana.yml'], @@ -71,6 +88,7 @@ test('correctly creates default environment in prod non-distributable mode.', () }; const defaultEnv = Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { dev: false }, configs: ['/some/other/path/some-kibana.yml'], @@ -92,6 +110,7 @@ test('correctly creates default environment if `--env.name` is supplied.', () => }; const defaultDevEnv = Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { envName: 'development' }, configs: ['/some/other/path/some-kibana.yml'], @@ -99,6 +118,7 @@ test('correctly creates default environment if `--env.name` is supplied.', () => ); const defaultProdEnv = Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { dev: false, envName: 'production' }, configs: ['/some/other/path/some-kibana.yml'], @@ -110,18 +130,17 @@ test('correctly creates default environment if `--env.name` is supplied.', () => }); test('correctly creates environment with constructor.', () => { - mockPackage.raw = { - branch: 'feature-v1', - version: 'v1', - build: { - distributable: true, - number: 100, - sha: 'feature-v1-build-sha', - }, - }; - const env = new Env( '/some/home/dir', + { + branch: 'feature-v1', + version: 'v1', + build: { + distributable: true, + number: 100, + sha: 'feature-v1-build-sha', + }, + }, getEnvOptions({ cliArgs: { dev: false }, configs: ['/some/other/path/some-kibana.yml'], @@ -134,6 +153,7 @@ test('correctly creates environment with constructor.', () => { test('pluginSearchPaths contains x-pack plugins path if --oss flag is false', () => { const env = new Env( '/some/home/dir', + packageInfos, getEnvOptions({ cliArgs: { oss: false }, }) @@ -145,6 +165,7 @@ test('pluginSearchPaths contains x-pack plugins path if --oss flag is false', () test('pluginSearchPaths does not contains x-pack plugins path if --oss flag is true', () => { const env = new Env( '/some/home/dir', + packageInfos, getEnvOptions({ cliArgs: { oss: true }, }) @@ -156,6 +177,7 @@ test('pluginSearchPaths does not contains x-pack plugins path if --oss flag is t test('pluginSearchPaths contains examples plugins path if --run-examples flag is true', () => { const env = new Env( '/some/home/dir', + packageInfos, getEnvOptions({ cliArgs: { runExamples: true }, }) @@ -167,6 +189,7 @@ test('pluginSearchPaths contains examples plugins path if --run-examples flag is test('pluginSearchPaths contains x-pack/examples plugins path if --run-examples flag is true', () => { const env = new Env( '/some/home/dir', + packageInfos, getEnvOptions({ cliArgs: { runExamples: true }, }) @@ -178,6 +201,7 @@ test('pluginSearchPaths contains x-pack/examples plugins path if --run-examples test('pluginSearchPaths does not contains examples plugins path if --run-examples flag is false', () => { const env = new Env( '/some/home/dir', + packageInfos, getEnvOptions({ cliArgs: { runExamples: false }, }) @@ -189,6 +213,7 @@ test('pluginSearchPaths does not contains examples plugins path if --run-example test('pluginSearchPaths does not contains x-pack/examples plugins path if --run-examples flag is false', () => { const env = new Env( '/some/home/dir', + packageInfos, getEnvOptions({ cliArgs: { runExamples: false }, }) diff --git a/src/core/server/config/env.ts b/packages/kbn-config/src/env.ts similarity index 87% rename from src/core/server/config/env.ts rename to packages/kbn-config/src/env.ts index d8b056996315b..250c7b72d47a9 100644 --- a/src/core/server/config/env.ts +++ b/packages/kbn-config/src/env.ts @@ -17,13 +17,10 @@ * under the License. */ -import { resolve, dirname } from 'path'; +import { resolve, join } from 'path'; +import loadJsonFile from 'load-json-file'; import { PackageInfo, EnvironmentMode } from './types'; -// `require` is necessary for this to work inside x-pack code as well -// eslint-disable-next-line @typescript-eslint/no-var-requires -const pkg = require('../../../../package.json'); - /** @internal */ export interface EnvOptions { configs: string[]; @@ -50,13 +47,26 @@ export interface CliArgs { dist: boolean; } +/** @internal */ +export interface RawPackageInfo { + branch: string; + version: string; + build: { + distributable?: boolean; + number: number; + sha: string; + }; +} + export class Env { /** * @internal */ - public static createDefault(options: EnvOptions): Env { - const repoRoot = dirname(require.resolve('../../../../package.json')); - return new Env(repoRoot, options); + public static createDefault(repoRoot: string, options: EnvOptions, pkg?: RawPackageInfo): Env { + if (!pkg) { + pkg = loadJsonFile.sync(join(repoRoot, 'package.json')) as RawPackageInfo; + } + return new Env(repoRoot, pkg, options); } /** @internal */ @@ -99,7 +109,7 @@ export class Env { /** * @internal */ - constructor(public readonly homeDir: string, options: EnvOptions) { + constructor(public readonly homeDir: string, pkg: RawPackageInfo, options: EnvOptions) { this.configDir = resolve(this.homeDir, 'config'); this.binDir = resolve(this.homeDir, 'bin'); this.logDir = resolve(this.homeDir, 'log'); diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts new file mode 100644 index 0000000000000..f02514a92e606 --- /dev/null +++ b/packages/kbn-config/src/index.ts @@ -0,0 +1,37 @@ +/* + * 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 { + applyDeprecations, + ConfigDeprecation, + ConfigDeprecationFactory, + configDeprecationFactory, + ConfigDeprecationLogger, + ConfigDeprecationProvider, + ConfigDeprecationWithContext, +} from './deprecation'; + +export { RawConfigurationProvider, RawConfigService, getConfigFromFiles } from './raw'; + +export { ConfigService, IConfigService } from './config_service'; +export { Config, ConfigPath, isConfigPath, hasConfigPathIntersection } from './config'; +export { ObjectToConfigAdapter } from './object_to_config_adapter'; +export { CliArgs, Env, RawPackageInfo } from './env'; +export { EnvironmentMode, PackageInfo } from './types'; +export { LegacyObjectToConfigAdapter, LegacyLoggingConfig } from './legacy'; diff --git a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap b/packages/kbn-config/src/legacy/__snapshots__/legacy_object_to_config_adapter.test.ts.snap similarity index 100% rename from src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap rename to packages/kbn-config/src/legacy/__snapshots__/legacy_object_to_config_adapter.test.ts.snap diff --git a/packages/kbn-config/src/legacy/index.ts b/packages/kbn-config/src/legacy/index.ts new file mode 100644 index 0000000000000..cad6b754e956c --- /dev/null +++ b/packages/kbn-config/src/legacy/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. + */ + +export { + LegacyObjectToConfigAdapter, + LegacyLoggingConfig, +} from './legacy_object_to_config_adapter'; diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts b/packages/kbn-config/src/legacy/legacy_object_to_config_adapter.test.ts similarity index 100% rename from src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts rename to packages/kbn-config/src/legacy/legacy_object_to_config_adapter.test.ts diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts b/packages/kbn-config/src/legacy/legacy_object_to_config_adapter.ts similarity index 92% rename from src/core/server/legacy/config/legacy_object_to_config_adapter.ts rename to packages/kbn-config/src/legacy/legacy_object_to_config_adapter.ts index 3e496648c3af9..e8fca8735a6d9 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts +++ b/packages/kbn-config/src/legacy/legacy_object_to_config_adapter.ts @@ -17,10 +17,17 @@ * under the License. */ -import { ConfigPath } from '../../config'; -import { ObjectToConfigAdapter } from '../../config/object_to_config_adapter'; -import { LoggingConfigType } from '../../logging/logging_config'; -import { LegacyVars } from '../types'; +import { ConfigPath } from '../config'; +import { ObjectToConfigAdapter } from '../object_to_config_adapter'; + +// TODO: fix once core schemas are moved to this package +type LoggingConfigType = any; + +/** + * @internal + * @deprecated + */ +export type LegacyVars = Record; /** * Represents logging config supported by the legacy platform. diff --git a/packages/kbn-config/src/mocks.ts b/packages/kbn-config/src/mocks.ts new file mode 100644 index 0000000000000..5ca5de3a328a2 --- /dev/null +++ b/packages/kbn-config/src/mocks.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +// these CANT be exported by the main entrypoint, as it cause ts check failures +// in `src/test` and `src/xpack/test` projects due to definition conflicts between +// mocha and jest declaring the same globals such as `it` or `beforeAll`, as the test +// files imports types from `core` that is importing the main `@kbn/config` entrypoint. +// For now, these should be imported using `import {} from '@kbn/config/target/mocks'` +export { configMock } from './config.mock'; +export { configServiceMock } from './config_service.mock'; +export { rawConfigServiceMock } from './raw/raw_config_service.mock'; +export { getEnvOptions } from './__mocks__/env'; diff --git a/src/core/server/config/object_to_config_adapter.test.ts b/packages/kbn-config/src/object_to_config_adapter.test.ts similarity index 100% rename from src/core/server/config/object_to_config_adapter.test.ts rename to packages/kbn-config/src/object_to_config_adapter.test.ts diff --git a/src/core/server/config/object_to_config_adapter.ts b/packages/kbn-config/src/object_to_config_adapter.ts similarity index 100% rename from src/core/server/config/object_to_config_adapter.ts rename to packages/kbn-config/src/object_to_config_adapter.ts diff --git a/src/core/server/config/__snapshots__/read_config.test.ts.snap b/packages/kbn-config/src/raw/__snapshots__/read_config.test.ts.snap similarity index 100% rename from src/core/server/config/__snapshots__/read_config.test.ts.snap rename to packages/kbn-config/src/raw/__snapshots__/read_config.test.ts.snap diff --git a/src/core/server/config/ensure_deep_object.test.ts b/packages/kbn-config/src/raw/ensure_deep_object.test.ts similarity index 100% rename from src/core/server/config/ensure_deep_object.test.ts rename to packages/kbn-config/src/raw/ensure_deep_object.test.ts diff --git a/src/core/server/config/ensure_deep_object.ts b/packages/kbn-config/src/raw/ensure_deep_object.ts similarity index 100% rename from src/core/server/config/ensure_deep_object.ts rename to packages/kbn-config/src/raw/ensure_deep_object.ts diff --git a/packages/kbn-config/src/raw/index.ts b/packages/kbn-config/src/raw/index.ts new file mode 100644 index 0000000000000..e32920fe64d67 --- /dev/null +++ b/packages/kbn-config/src/raw/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 { RawConfigService, RawConfigurationProvider } from './raw_config_service'; +export { getConfigFromFiles } from './read_config'; diff --git a/src/core/server/config/raw_config_service.mock.ts b/packages/kbn-config/src/raw/raw_config_service.mock.ts similarity index 99% rename from src/core/server/config/raw_config_service.mock.ts rename to packages/kbn-config/src/raw/raw_config_service.mock.ts index 73a3b5cc9e4d0..53ea797dac9e5 100644 --- a/src/core/server/config/raw_config_service.mock.ts +++ b/packages/kbn-config/src/raw/raw_config_service.mock.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import type { PublicMethodsOf } from '@kbn/utility-types'; import { RawConfigService } from './raw_config_service'; import { Observable, of } from 'rxjs'; diff --git a/src/core/server/config/raw_config_service.test.mocks.ts b/packages/kbn-config/src/raw/raw_config_service.test.mocks.ts similarity index 100% rename from src/core/server/config/raw_config_service.test.mocks.ts rename to packages/kbn-config/src/raw/raw_config_service.test.mocks.ts diff --git a/src/core/server/config/raw_config_service.test.ts b/packages/kbn-config/src/raw/raw_config_service.test.ts similarity index 98% rename from src/core/server/config/raw_config_service.test.ts rename to packages/kbn-config/src/raw/raw_config_service.test.ts index 8846ea3847f79..444d67985250b 100644 --- a/src/core/server/config/raw_config_service.test.ts +++ b/packages/kbn-config/src/raw/raw_config_service.test.ts @@ -20,7 +20,7 @@ import { mockGetConfigFromFiles } from './raw_config_service.test.mocks'; import { first } from 'rxjs/operators'; -import { RawConfigService } from '.'; +import { RawConfigService } from './raw_config_service'; const configFile = '/config/kibana.yml'; const anotherConfigFile = '/config/kibana.dev.yml'; diff --git a/src/core/server/config/raw_config_service.ts b/packages/kbn-config/src/raw/raw_config_service.ts similarity index 100% rename from src/core/server/config/raw_config_service.ts rename to packages/kbn-config/src/raw/raw_config_service.ts diff --git a/src/core/server/config/read_config.test.ts b/packages/kbn-config/src/raw/read_config.test.ts similarity index 96% rename from src/core/server/config/read_config.test.ts rename to packages/kbn-config/src/raw/read_config.test.ts index 46b75f28eb987..89b73c5d4e26a 100644 --- a/src/core/server/config/read_config.test.ts +++ b/packages/kbn-config/src/raw/read_config.test.ts @@ -20,7 +20,7 @@ import { relative, resolve } from 'path'; import { getConfigFromFiles } from './read_config'; -const fixtureFile = (name: string) => `${__dirname}/__fixtures__/${name}`; +const fixtureFile = (name: string) => resolve(`${__dirname}/../../__fixtures__/${name}`); test('reads single yaml from file system and parses to json', () => { const config = getConfigFromFiles([fixtureFile('config.yml')]); diff --git a/src/core/server/config/read_config.ts b/packages/kbn-config/src/raw/read_config.ts similarity index 100% rename from src/core/server/config/read_config.ts rename to packages/kbn-config/src/raw/read_config.ts diff --git a/src/core/server/config/types.ts b/packages/kbn-config/src/types.ts similarity index 100% rename from src/core/server/config/types.ts rename to packages/kbn-config/src/types.ts diff --git a/packages/kbn-config/tsconfig.json b/packages/kbn-config/tsconfig.json new file mode 100644 index 0000000000000..ba00ddfa6adb6 --- /dev/null +++ b/packages/kbn-config/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "declaration": true, + "outDir": "./target", + "stripInternal": false, + "declarationMap": true, + "types": ["jest", "node"] + }, + "include": ["./src/**/*.ts"], + "exclude": ["target"] +} diff --git a/packages/kbn-config/yarn.lock b/packages/kbn-config/yarn.lock new file mode 120000 index 0000000000000..3f82ebc9cdbae --- /dev/null +++ b/packages/kbn-config/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-logging/README.md b/packages/kbn-logging/README.md new file mode 100644 index 0000000000000..8bde73722deb0 --- /dev/null +++ b/packages/kbn-logging/README.md @@ -0,0 +1,61 @@ +# kbn-logging + +Base types for the kibana platform logging system. + +Note that this package currently only contains logging types. The only concrete implementation +is still in `core` for now. + +- [Loggers, Appenders and Layouts](#loggers-appenders-and-layouts) +- [Logger hierarchy](#logger-hierarchy) +- [Log level](#log-level) +- [Layouts](#layouts) + +The way logging works in Kibana is inspired by `log4j 2` logging framework used by [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html#logging). +The main idea is to have consistent logging behaviour (configuration, log format etc.) across the entire Elastic Stack +where possible. + +## Loggers, Appenders and Layouts + +Kibana logging system has three main components: _loggers_, _appenders_ and _layouts_. These components allow us to log +messages according to message type and level, and to control how these messages are formatted and where the final logs +will be displayed or stored. + +__Loggers__ define what logging settings should be applied at the particular context. + +__Appenders__ define where log messages are displayed (eg. stdout or console) and stored (eg. file on the disk). + +__Layouts__ define how log messages are formatted and what type of information they include. + + +## Logger hierarchy + +Every logger has its unique name or context that follows hierarchical naming rule. The logger is considered to be an +ancestor of another logger if its name followed by a `.` is a prefix of the descendant logger name. For example logger +with `a.b` context is an ancestor of logger with `a.b.c` context. All top-level loggers are descendants of special +logger with `root` context that resides at the top of the logger hierarchy. This logger always exists and +fully configured. + +Developer can configure _log level_ and _appenders_ that should be used within particular context. If logger configuration +specifies only _log level_ then _appenders_ configuration will be inherited from the ancestor logger. + +__Note:__ in the current implementation log messages are only forwarded to appenders configured for a particular logger +context or to appenders of the closest ancestor if current logger doesn't have any appenders configured. That means that +we __don't support__ so called _appender additivity_ when log messages are forwarded to _every_ distinct appender within +ancestor chain including `root`. + +## Log level + +Currently we support the following log levels: _all_, _fatal_, _error_, _warn_, _info_, _debug_, _trace_, _off_. +Levels are ordered, so _all_ > _fatal_ > _error_ > _warn_ > _info_ > _debug_ > _trace_ > _off_. +A log record is being logged by the logger if its level is higher than or equal to the level of its logger. Otherwise, +the log record is ignored. + +The _all_ and _off_ levels can be used only in configuration and are just handy shortcuts that allow developer to log every +log record or disable logging entirely for the specific context. + +## Layouts + +Every appender should know exactly how to format log messages before they are written to the console or file on the disk. +This behaviour is controlled by the layouts and configured through `appender.layout` configuration property for every +custom appender. Currently we don't define any default layout for the custom appenders, so one should always make the choice +explicitly. diff --git a/packages/kbn-logging/package.json b/packages/kbn-logging/package.json new file mode 100644 index 0000000000000..922d562733622 --- /dev/null +++ b/packages/kbn-logging/package.json @@ -0,0 +1,18 @@ +{ + "name": "@kbn/logging", + "version": "1.0.0", + "private": true, + "license": "Apache-2.0", + "main": "./target/index.js", + "scripts": { + "build": "tsc", + "kbn:bootstrap": "yarn build", + "kbn:watch": "yarn build --watch" + }, + "dependencies": { + "@kbn/std": "1.0.0" + }, + "devDependencies": { + "typescript": "4.0.2" + } +} diff --git a/packages/kbn-logging/src/appenders.ts b/packages/kbn-logging/src/appenders.ts new file mode 100644 index 0000000000000..346d3d6dd1068 --- /dev/null +++ b/packages/kbn-logging/src/appenders.ts @@ -0,0 +1,39 @@ +/* + * 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 { LogRecord } from './log_record'; + +/** + * Entity that can append `LogRecord` instances to file, stdout, memory or whatever + * is implemented internally. It's supposed to be used by `Logger`. + * @internal + */ +export interface Appender { + append(record: LogRecord): void; +} + +/** + * This interface should be additionally implemented by the `Appender`'s if they are supposed + * to be properly disposed. It's intentionally separated from `Appender` interface so that `Logger` + * that interacts with `Appender` doesn't have control over appender lifetime. + * @internal + */ +export interface DisposableAppender extends Appender { + dispose: () => void; +} diff --git a/packages/kbn-logging/src/index.ts b/packages/kbn-logging/src/index.ts new file mode 100644 index 0000000000000..d06218ac2eeb5 --- /dev/null +++ b/packages/kbn-logging/src/index.ts @@ -0,0 +1,25 @@ +/* + * 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 { LogLevel, LogLevelId } from './log_level'; +export { LogRecord } from './log_record'; +export { Logger, LogMeta } from './logger'; +export { LoggerFactory } from './logger_factory'; +export { Layout } from './layout'; +export { Appender, DisposableAppender } from './appenders'; diff --git a/packages/kbn-logging/src/layout.ts b/packages/kbn-logging/src/layout.ts new file mode 100644 index 0000000000000..75556eab88bb6 --- /dev/null +++ b/packages/kbn-logging/src/layout.ts @@ -0,0 +1,28 @@ +/* + * 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 { LogRecord } from './log_record'; + +/** + * Entity that can format `LogRecord` instance into a string. + * @internal + */ +export interface Layout { + format(record: LogRecord): string; +} diff --git a/src/core/server/logging/log_level.test.ts b/packages/kbn-logging/src/log_level.test.ts similarity index 100% rename from src/core/server/logging/log_level.test.ts rename to packages/kbn-logging/src/log_level.test.ts diff --git a/src/core/server/logging/log_level.ts b/packages/kbn-logging/src/log_level.ts similarity index 100% rename from src/core/server/logging/log_level.ts rename to packages/kbn-logging/src/log_level.ts diff --git a/src/core/server/logging/log_record.ts b/packages/kbn-logging/src/log_record.ts similarity index 100% rename from src/core/server/logging/log_record.ts rename to packages/kbn-logging/src/log_record.ts diff --git a/packages/kbn-logging/src/logger.ts b/packages/kbn-logging/src/logger.ts new file mode 100644 index 0000000000000..50e002a87fc52 --- /dev/null +++ b/packages/kbn-logging/src/logger.ts @@ -0,0 +1,96 @@ +/* + * 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 { LogRecord } from './log_record'; + +/** + * Contextual metadata + * + * @public + */ +export interface LogMeta { + [key: string]: any; +} + +/** + * Logger exposes all the necessary methods to log any type of information and + * this is the interface used by the logging consumers including plugins. + * + * @public + */ +export interface Logger { + /** + * Log messages at the most detailed log level + * + * @param message - The log message + * @param meta - + */ + trace(message: string, meta?: LogMeta): void; + + /** + * Log messages useful for debugging and interactive investigation + * @param message - The log message + * @param meta - + */ + debug(message: string, meta?: LogMeta): void; + + /** + * Logs messages related to general application flow + * @param message - The log message + * @param meta - + */ + info(message: string, meta?: LogMeta): void; + + /** + * Logs abnormal or unexpected errors or messages + * @param errorOrMessage - An Error object or message string to log + * @param meta - + */ + warn(errorOrMessage: string | Error, meta?: LogMeta): void; + + /** + * Logs abnormal or unexpected errors or messages that caused a failure in the application flow + * + * @param errorOrMessage - An Error object or message string to log + * @param meta - + */ + error(errorOrMessage: string | Error, meta?: LogMeta): void; + + /** + * Logs abnormal or unexpected errors or messages that caused an unrecoverable failure + * + * @param errorOrMessage - An Error object or message string to log + * @param meta - + */ + fatal(errorOrMessage: string | Error, meta?: LogMeta): void; + + /** @internal */ + log(record: LogRecord): void; + + /** + * Returns a new {@link Logger} instance extending the current logger context. + * + * @example + * ```typescript + * const logger = loggerFactory.get('plugin', 'service'); // 'plugin.service' context + * const subLogger = logger.get('feature'); // 'plugin.service.feature' context + * ``` + */ + get(...childContextPaths: string[]): Logger; +} diff --git a/src/core/server/logging/logger_factory.ts b/packages/kbn-logging/src/logger_factory.ts similarity index 100% rename from src/core/server/logging/logger_factory.ts rename to packages/kbn-logging/src/logger_factory.ts diff --git a/packages/kbn-logging/src/mocks/index.ts b/packages/kbn-logging/src/mocks/index.ts new file mode 100644 index 0000000000000..c7be3a1b8ce8c --- /dev/null +++ b/packages/kbn-logging/src/mocks/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { MockedLogger, loggerMock } from './logger.mock'; diff --git a/packages/kbn-logging/src/mocks/logger.mock.ts b/packages/kbn-logging/src/mocks/logger.mock.ts new file mode 100644 index 0000000000000..1a4c7a2dadf68 --- /dev/null +++ b/packages/kbn-logging/src/mocks/logger.mock.ts @@ -0,0 +1,70 @@ +/* + * 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 { Logger } from '../logger'; + +export type MockedLogger = jest.Mocked & { context: string[] }; + +const createLoggerMock = (context: string[] = []) => { + const mockLog: MockedLogger = { + context, + debug: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + log: jest.fn(), + trace: jest.fn(), + warn: jest.fn(), + get: jest.fn(), + }; + mockLog.get.mockImplementation((...ctx) => ({ + ctx, + ...mockLog, + })); + + return mockLog; +}; + +const clearLoggerMock = (logger: MockedLogger) => { + logger.debug.mockClear(); + logger.info.mockClear(); + logger.warn.mockClear(); + logger.error.mockClear(); + logger.trace.mockClear(); + logger.fatal.mockClear(); + logger.log.mockClear(); +}; + +const collectLoggerMock = (logger: MockedLogger) => { + return { + debug: logger.debug.mock.calls, + error: logger.error.mock.calls, + fatal: logger.fatal.mock.calls, + info: logger.info.mock.calls, + log: logger.log.mock.calls, + trace: logger.trace.mock.calls, + warn: logger.warn.mock.calls, + }; +}; + +export const loggerMock = { + create: createLoggerMock, + clear: clearLoggerMock, + collect: collectLoggerMock, +}; diff --git a/packages/kbn-logging/tsconfig.json b/packages/kbn-logging/tsconfig.json new file mode 100644 index 0000000000000..c55c05de30a52 --- /dev/null +++ b/packages/kbn-logging/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target", + "stripInternal": false, + "declaration": true, + "declarationMap": true, + "types": ["jest", "node"] + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/kbn-logging/yarn.lock b/packages/kbn-logging/yarn.lock new file mode 120000 index 0000000000000..3f82ebc9cdbae --- /dev/null +++ b/packages/kbn-logging/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-std/package.json b/packages/kbn-std/package.json index 4c67706b45d27..2cc9fd72082be 100644 --- a/packages/kbn-std/package.json +++ b/packages/kbn-std/package.json @@ -15,7 +15,6 @@ }, "dependencies": { "@kbn/utility-types": "1.0.0", - "lodash": "^4.17.15", - "query-string": "5.1.1" + "lodash": "^4.17.15" } } diff --git a/packages/kbn-std/src/index.ts b/packages/kbn-std/src/index.ts index 7cf70a0e28e2c..8cffcd43d7537 100644 --- a/packages/kbn-std/src/index.ts +++ b/packages/kbn-std/src/index.ts @@ -24,6 +24,6 @@ export { mapToObject } from './map_to_object'; export { merge } from './merge'; export { pick } from './pick'; export { withTimeout } from './promise'; -export { isRelativeUrl, modifyUrl, URLMeaningfulParts } from './url'; +export { isRelativeUrl, modifyUrl, URLMeaningfulParts, ParsedQuery } from './url'; export { unset } from './unset'; export { getFlattenedObject } from './get_flattened_object'; diff --git a/packages/kbn-std/src/url.ts b/packages/kbn-std/src/url.ts index 910fc8eaa4381..7a0f08130816d 100644 --- a/packages/kbn-std/src/url.ts +++ b/packages/kbn-std/src/url.ts @@ -16,9 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { ParsedQuery } from 'query-string'; + import { format as formatUrl, parse as parseUrl, UrlObject } from 'url'; +// duplicate type from 'query-string' to avoid adding the d.ts file to all packages depending on kbn-std +export interface ParsedQuery { + [key: string]: T | T[] | null | undefined; +} + /** * We define our own typings because the current version of @types/node * declares properties to be optional "hostname?: string". diff --git a/packages/kbn-std/tsconfig.json b/packages/kbn-std/tsconfig.json index 5c86ad17a90e9..fd186a6e43d1c 100644 --- a/packages/kbn-std/tsconfig.json +++ b/packages/kbn-std/tsconfig.json @@ -8,9 +8,6 @@ "declarationMap": true, "types": ["jest", "node"] }, - "include": [ - "./src/**/*.ts", - "../../typings/query_string.d.ts" - ], + "include": ["./src/**/*.ts"], "exclude": ["target"] } diff --git a/src/cli/serve/integration_tests/reload_logging_config.test.ts b/src/cli/serve/integration_tests/reload_logging_config.test.ts index 35391b9b58ecc..55f71ea2401db 100644 --- a/src/cli/serve/integration_tests/reload_logging_config.test.ts +++ b/src/cli/serve/integration_tests/reload_logging_config.test.ts @@ -26,8 +26,7 @@ import Del from 'del'; import * as Rx from 'rxjs'; import { map, filter, take } from 'rxjs/operators'; import { safeDump } from 'js-yaml'; - -import { getConfigFromFiles } from '../../../core/server/config/read_config'; +import { getConfigFromFiles } from '@kbn/config'; const legacyConfig = follow('__fixtures__/reload_logging_config/kibana.test.yml'); const configFileLogConsole = follow( diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index d0b9e115bd524..d22c7ae805827 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -7,6 +7,8 @@ import { Action } from 'history'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from 'boom'; +import { ConfigPath } from '@kbn/config'; +import { EnvironmentMode } from '@kbn/config'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; @@ -19,8 +21,11 @@ import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType } from 'src/core/server/kibana_config'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; +import { Logger } from '@kbn/logging'; +import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; import { Observable } from 'rxjs'; +import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/server/types'; @@ -576,15 +581,7 @@ export interface DocLinksStart { }; } -// @public (undocumented) -export interface EnvironmentMode { - // (undocumented) - dev: boolean; - // (undocumented) - name: 'development' | 'production'; - // (undocumented) - prod: boolean; -} +export { EnvironmentMode } // @public export interface ErrorToastOptions extends ToastOptions { @@ -912,19 +909,7 @@ export interface OverlayStart { openModal: OverlayModalStart['open']; } -// @public (undocumented) -export interface PackageInfo { - // (undocumented) - branch: string; - // (undocumented) - buildNum: number; - // (undocumented) - buildSha: string; - // (undocumented) - dist: boolean; - // (undocumented) - version: string; -} +export { PackageInfo } // @public export interface Plugin { diff --git a/src/core/server/bootstrap.ts b/src/core/server/bootstrap.ts index c0cbe3a39ff54..ff1a5c0340c46 100644 --- a/src/core/server/bootstrap.ts +++ b/src/core/server/bootstrap.ts @@ -60,7 +60,15 @@ export async function bootstrap({ return; } - const env = Env.createDefault({ + // `bootstrap` is exported from the `src/core/server/index` module, + // meaning that any test importing, implicitly or explicitly, anything concrete + // from `core/server` will load `dev-utils`. As some tests are mocking the `fs` package, + // and as `REPO_ROOT` is initialized on the fly when importing `dev-utils` and requires + // the `fs` package, it causes failures. This is why we use a dynamic `require` here. + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { REPO_ROOT } = require('@kbn/utils'); + + const env = Env.createDefault(REPO_ROOT, { configs, cliArgs, isDevClusterMaster: isMaster && cliArgs.dev && features.isClusterModeSupported, 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 b4d620965b047..17f2c77bbf660 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -18,16 +18,18 @@ */ import supertest from 'supertest'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { HttpService, InternalHttpServiceSetup } from '../../http'; import { contextServiceMock } from '../../context/context_service.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { Env } from '../../config'; -import { getEnvOptions } from '../../config/__mocks__/env'; +import { getEnvOptions } from '../../config/mocks'; import { CapabilitiesService, CapabilitiesSetup } from '..'; import { createHttpServer } from '../../http/test_utils'; const coreId = Symbol('core'); -const env = Env.createDefault(getEnvOptions()); + +const env = Env.createDefault(REPO_ROOT, getEnvOptions()); describe('CapabilitiesService', () => { let server: HttpService; diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index adf0f52339366..7a69dc2fa726e 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -17,9 +17,8 @@ * under the License. */ +import { configDeprecationFactory, applyDeprecations } from '@kbn/config'; import { coreDeprecationProvider } from './core_deprecations'; -import { configDeprecationFactory } from './deprecation_factory'; -import { applyDeprecations } from './apply_deprecations'; const initialEnv = { ...process.env }; diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 2b8b8e383da24..2701edcf44e03 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -18,7 +18,7 @@ */ import { has, get } from 'lodash'; -import { ConfigDeprecationProvider, ConfigDeprecation } from './types'; +import { ConfigDeprecationProvider, ConfigDeprecation } from '@kbn/config'; const configPathDeprecation: ConfigDeprecation = (settings, fromPath, log) => { if (has(process.env, 'CONFIG_PATH')) { diff --git a/src/core/server/config/deprecation/index.ts b/src/core/server/config/deprecation/index.ts index f79338665166b..9e8e2e5a2bddf 100644 --- a/src/core/server/config/deprecation/index.ts +++ b/src/core/server/config/deprecation/index.ts @@ -17,13 +17,4 @@ * under the License. */ -export { - ConfigDeprecation, - ConfigDeprecationWithContext, - ConfigDeprecationLogger, - ConfigDeprecationFactory, - ConfigDeprecationProvider, -} from './types'; -export { configDeprecationFactory } from './deprecation_factory'; export { coreDeprecationProvider } from './core_deprecations'; -export { applyDeprecations } from './apply_deprecations'; diff --git a/src/core/server/config/index.ts b/src/core/server/config/index.ts index 04dc402d35b22..f69a5d081cc8a 100644 --- a/src/core/server/config/index.ts +++ b/src/core/server/config/index.ts @@ -17,17 +17,25 @@ * under the License. */ -export { ConfigService, IConfigService } from './config_service'; -export { RawConfigService, RawConfigurationProvider } from './raw_config_service'; -export { Config, ConfigPath, isConfigPath, hasConfigPathIntersection } from './config'; -export { ObjectToConfigAdapter } from './object_to_config_adapter'; -export { CliArgs, Env } from './env'; +export { coreDeprecationProvider } from './deprecation'; + export { + ConfigService, + IConfigService, + RawConfigService, + RawConfigurationProvider, + Config, + ConfigPath, + isConfigPath, + hasConfigPathIntersection, + ObjectToConfigAdapter, + CliArgs, + Env, ConfigDeprecation, ConfigDeprecationLogger, ConfigDeprecationProvider, ConfigDeprecationFactory, - coreDeprecationProvider, -} from './deprecation'; - -export { EnvironmentMode, PackageInfo } from './types'; + EnvironmentMode, + PackageInfo, + LegacyObjectToConfigAdapter, +} from '@kbn/config'; diff --git a/src/core/server/config/mocks.ts b/src/core/server/config/mocks.ts new file mode 100644 index 0000000000000..960dda496e635 --- /dev/null +++ b/src/core/server/config/mocks.ts @@ -0,0 +1,25 @@ +/* + * 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 { + getEnvOptions, + rawConfigServiceMock, + configServiceMock, + configMock, +} from '@kbn/config/target/mocks'; diff --git a/src/core/server/core_context.mock.ts b/src/core/server/core_context.mock.ts index 2b887358818e3..bbf04783278f7 100644 --- a/src/core/server/core_context.mock.ts +++ b/src/core/server/core_context.mock.ts @@ -16,15 +16,16 @@ * specific language governing permissions and limitations * under the License. */ + +import { REPO_ROOT } from '@kbn/dev-utils'; import { CoreContext } from './core_context'; -import { getEnvOptions } from './config/__mocks__/env'; import { Env, IConfigService } from './config'; +import { configServiceMock, getEnvOptions } from './config/mocks'; import { loggingSystemMock } from './logging/logging_system.mock'; -import { configServiceMock } from './config/config_service.mock'; import { ILoggingSystem } from './logging'; function create({ - env = Env.createDefault(getEnvOptions()), + env = Env.createDefault(REPO_ROOT, getEnvOptions()), logger = loggingSystemMock.create(), configService = configServiceMock.create(), }: { diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index 648eaaf8a97f2..3912c68836bb2 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -23,8 +23,8 @@ import { mockReadPkcs12Truststore, } from './elasticsearch_config.test.mocks'; +import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; import { ElasticsearchConfig, config } from './elasticsearch_config'; -import { applyDeprecations, configDeprecationFactory } from '../config/deprecation'; const CONFIG_PATH = 'elasticsearch'; diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 49f5c8dd98790..ce82410f6061e 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -20,10 +20,10 @@ import { MockLegacyClusterClient, MockClusterClient } from './elasticsearch_service.test.mocks'; import { BehaviorSubject } from 'rxjs'; import { first } from 'rxjs/operators'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; +import { configServiceMock, getEnvOptions } from '../config/mocks'; import { CoreContext } from '../core_context'; -import { configServiceMock } from '../config/config_service.mock'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { auditTrailServiceMock } from '../audit_trail/audit_trail_service.mock'; @@ -64,7 +64,7 @@ let mockClusterClientInstance: ReturnType; beforeEach(() => { - env = Env.createDefault(getEnvOptions()); + env = Env.createDefault(REPO_ROOT, getEnvOptions()); coreContext = { coreId: Symbol(), env, logger, configService: configService as any }; elasticsearchService = new ElasticsearchService(coreContext); diff --git a/src/core/server/environment/environment_service.test.ts b/src/core/server/environment/environment_service.test.ts index f6cffb5e26a9e..e4dcfc49c1130 100644 --- a/src/core/server/environment/environment_service.test.ts +++ b/src/core/server/environment/environment_service.test.ts @@ -18,13 +18,14 @@ */ import { BehaviorSubject } from 'rxjs'; + import { EnvironmentService } from './environment_service'; import { resolveInstanceUuid } from './resolve_uuid'; import { createDataFolder } from './create_data_folder'; import { writePidFile } from './write_pid_file'; import { CoreContext } from '../core_context'; -import { configServiceMock } from '../config/config_service.mock'; +import { configServiceMock } from '../config/mocks'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { mockCoreContext } from '../core_context.mock'; diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index 8e5dec7d4eadd..b7ade0cbde0fc 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -18,20 +18,20 @@ */ import request from 'request'; import supertest from 'supertest'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { ByteSizeValue } from '@kbn/config-schema'; import { BehaviorSubject } from 'rxjs'; import { CoreContext } from '../core_context'; import { HttpService } from './http_service'; import { KibanaRequest } from './router'; - 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 { loggingSystemMock } from '../logging/logging_system.mock'; - +import { getEnvOptions, configServiceMock } from '../config/mocks'; import { httpServerMock } from './http_server.mocks'; + import { createCookieSessionStorageFactory } from './cookie_session_storage'; let server: HttpService; @@ -72,7 +72,7 @@ configService.atPath.mockReturnValue( beforeEach(() => { logger = loggingSystemMock.create(); - env = Env.createDefault(getEnvOptions()); + env = Env.createDefault(REPO_ROOT, getEnvOptions()); coreContext = { coreId: Symbol(), env, logger, configService: configService as any }; server = new HttpService(coreContext); diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 3e38f6a6d384d..f81708145edc4 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -22,7 +22,6 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { CspConfig } from '../csp'; import { mockRouter, RouterMock } from './router/router.mock'; -import { configMock } from '../config/config.mock'; import { InternalHttpServiceSetup, HttpServiceSetup, @@ -37,6 +36,7 @@ import { sessionStorageMock } from './cookie_session_storage.mocks'; import { OnPostAuthToolkit } from './lifecycle/on_post_auth'; import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; import { OnPreResponseToolkit } from './lifecycle/on_pre_response'; +import { configMock } from '../config/mocks'; type BasePathMocked = jest.Mocked; type AuthMocked = jest.Mocked; diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index 3d759b427d9fb..11cea88fa0dd2 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -21,17 +21,18 @@ import { mockHttpServer } from './http_service.test.mocks'; import { noop } from 'lodash'; import { BehaviorSubject } from 'rxjs'; +import { REPO_ROOT } from '@kbn/dev-utils'; +import { getEnvOptions } from '../config/mocks'; import { HttpService } from '.'; import { HttpConfigType, config } from './http_config'; import { httpServerMock } from './http_server.mocks'; import { ConfigService, Env } from '../config'; 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 = loggingSystemMock.create(); -const env = Env.createDefault(getEnvOptions()); +const env = Env.createDefault(REPO_ROOT, getEnvOptions()); const coreId = Symbol(); const createConfigService = (value: Partial = {}) => { @@ -115,7 +116,7 @@ test('spins up notReady server until started if configured with `autoListen:true const service = new HttpService({ coreId, configService, - env: new Env('.', getEnvOptions()), + env: Env.createDefault(REPO_ROOT, getEnvOptions()), logger, }); @@ -263,7 +264,7 @@ test('does not start http server if process is dev cluster master', async () => const service = new HttpService({ coreId, configService, - env: new Env('.', getEnvOptions({ isDevClusterMaster: true })), + env: Env.createDefault(REPO_ROOT, getEnvOptions({ isDevClusterMaster: true })), logger, }); @@ -288,7 +289,7 @@ test('does not start http server if configured with `autoListen:false`', async ( const service = new HttpService({ coreId, configService, - env: new Env('.', getEnvOptions()), + env: Env.createDefault(REPO_ROOT, getEnvOptions()), logger, }); diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts index 8d70b5c3ad119..a964130550bf5 100644 --- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts +++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts @@ -26,7 +26,7 @@ import { HttpService } from '../http_service'; import { HttpServerSetup } from '../http_server'; import { IRouter, RouteRegistrar } from '../router'; -import { configServiceMock } from '../../config/config_service.mock'; +import { configServiceMock } from '../../config/mocks'; import { contextServiceMock } from '../../context/context_service.mock'; // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index c3afae108027e..b4d91926f13f4 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -18,16 +18,16 @@ */ import { BehaviorSubject } from 'rxjs'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { ByteSizeValue } from '@kbn/config-schema'; import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; import { HttpService } from './http_service'; import { CoreContext } from '../core_context'; -import { configServiceMock } from '../config/config_service.mock'; +import { getEnvOptions, configServiceMock } from '../config/mocks'; import { loggingSystemMock } from '../logging/logging_system.mock'; const coreId = Symbol('core'); -const env = Env.createDefault(getEnvOptions()); +const env = Env.createDefault(REPO_ROOT, getEnvOptions()); const logger = loggingSystemMock.create(); diff --git a/src/core/server/legacy/config/ensure_valid_configuration.test.ts b/src/core/server/legacy/config/ensure_valid_configuration.test.ts index d8917b46eba62..702840b8a0a6a 100644 --- a/src/core/server/legacy/config/ensure_valid_configuration.test.ts +++ b/src/core/server/legacy/config/ensure_valid_configuration.test.ts @@ -19,7 +19,7 @@ import { ensureValidConfiguration } from './ensure_valid_configuration'; import { getUnusedConfigKeys } from './get_unused_config_keys'; -import { configServiceMock } from '../../config/config_service.mock'; +import { configServiceMock } from '../../config/mocks'; jest.mock('./get_unused_config_keys'); diff --git a/src/core/server/legacy/config/index.ts b/src/core/server/legacy/config/index.ts index b56b83ca324cb..c8781945d7cbc 100644 --- a/src/core/server/legacy/config/index.ts +++ b/src/core/server/legacy/config/index.ts @@ -18,4 +18,3 @@ */ export { ensureValidConfiguration } from './ensure_valid_configuration'; -export { LegacyObjectToConfigAdapter } from './legacy_object_to_config_adapter'; diff --git a/src/core/server/legacy/index.ts b/src/core/server/legacy/index.ts index 208e9b1167253..6b0963e3129c6 100644 --- a/src/core/server/legacy/index.ts +++ b/src/core/server/legacy/index.ts @@ -18,7 +18,7 @@ */ /** @internal */ -export { LegacyObjectToConfigAdapter, ensureValidConfiguration } from './config'; +export { ensureValidConfiguration } from './config'; /** @internal */ export { LegacyInternals } from './legacy_internals'; /** @internal */ diff --git a/src/core/server/legacy/integration_tests/logging.test.ts b/src/core/server/legacy/integration_tests/logging.test.ts index 2ebe17ea92978..dfcecd3f36b36 100644 --- a/src/core/server/legacy/integration_tests/logging.test.ts +++ b/src/core/server/legacy/integration_tests/logging.test.ts @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ + +import { LegacyLoggingConfig } from '@kbn/config'; import * as kbnTestServer from '../../../test_helpers/kbn_server'; import { @@ -23,8 +25,6 @@ import { getLegacyPlatformLogsFromMock, } from '../../logging/integration_tests/utils'; -import { LegacyLoggingConfig } from '../config/legacy_object_to_config_adapter'; - function createRoot(legacyLoggingConfig: LegacyLoggingConfig = {}) { return kbnTestServer.createRoot({ migrations: { skip: true }, // otherwise stuck in polling ES diff --git a/src/core/server/legacy/legacy_internals.test.ts b/src/core/server/legacy/legacy_internals.test.ts index 67f2f433d4570..935e36a989a0c 100644 --- a/src/core/server/legacy/legacy_internals.test.ts +++ b/src/core/server/legacy/legacy_internals.test.ts @@ -19,7 +19,7 @@ import { Server } from 'hapi'; -import { configMock } from '../config/config.mock'; +import { configMock } from '../config/mocks'; import { httpServiceMock } from '../http/http_service.mock'; import { httpServerMock } from '../http/http_server.mocks'; import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks'; diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index f3ce89f83a610..549252723be3f 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -25,16 +25,16 @@ import { } from './legacy_service.test.mocks'; import { BehaviorSubject, throwError } from 'rxjs'; +import { REPO_ROOT } from '@kbn/dev-utils'; // @ts-expect-error js file to remove TS dependency on cli import { ClusterManager as MockClusterManager } from './cluster_manager'; import KbnServer from '../../../legacy/server/kbn_server'; import { Config, Env, ObjectToConfigAdapter } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; import { BasePathProxyServer } from '../http'; import { DiscoveredPlugin } from '../plugins'; -import { configServiceMock } from '../config/config_service.mock'; +import { getEnvOptions, configServiceMock } from '../config/mocks'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { contextServiceMock } from '../context/context_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; @@ -68,7 +68,7 @@ let environmentSetup: ReturnType { coreId = Symbol(); - env = Env.createDefault(getEnvOptions()); + env = Env.createDefault(REPO_ROOT, getEnvOptions()); configService = configServiceMock.create(); environmentSetup = environmentServiceMock.createSetupContract(); @@ -363,6 +363,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { const devClusterLegacyService = new LegacyService({ coreId, env: Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { silent: true, basePath: false }, isDevClusterMaster: true, @@ -391,6 +392,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { const devClusterLegacyService = new LegacyService({ coreId, env: Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { quiet: true, basePath: true }, isDevClusterMaster: true, diff --git a/src/core/server/legacy/logging/appenders/legacy_appender.test.ts b/src/core/server/legacy/logging/appenders/legacy_appender.test.ts index 538d987e781d0..697e5bc37d602 100644 --- a/src/core/server/legacy/logging/appenders/legacy_appender.test.ts +++ b/src/core/server/legacy/logging/appenders/legacy_appender.test.ts @@ -19,8 +19,7 @@ jest.mock('../legacy_logging_server'); -import { LogLevel } from '../../../logging/log_level'; -import { LogRecord } from '../../../logging/log_record'; +import { LogRecord, LogLevel } from '../../../logging'; import { LegacyLoggingServer } from '../legacy_logging_server'; import { LegacyAppender } from './legacy_appender'; diff --git a/src/core/server/legacy/logging/appenders/legacy_appender.ts b/src/core/server/legacy/logging/appenders/legacy_appender.ts index a5d36423ba4c6..67337c7d67629 100644 --- a/src/core/server/legacy/logging/appenders/legacy_appender.ts +++ b/src/core/server/legacy/logging/appenders/legacy_appender.ts @@ -18,8 +18,7 @@ */ import { schema } from '@kbn/config-schema'; -import { DisposableAppender } from '../../../logging/appenders/appenders'; -import { LogRecord } from '../../../logging/log_record'; +import { DisposableAppender, LogRecord } from '../../../logging'; import { LegacyLoggingServer } from '../legacy_logging_server'; import { LegacyVars } from '../../types'; diff --git a/src/core/server/legacy/logging/legacy_logging_server.test.ts b/src/core/server/legacy/logging/legacy_logging_server.test.ts index 6dca3a199728e..2f6c34e0fc5d6 100644 --- a/src/core/server/legacy/logging/legacy_logging_server.test.ts +++ b/src/core/server/legacy/logging/legacy_logging_server.test.ts @@ -20,7 +20,7 @@ jest.mock('../../../../legacy/server/config'); jest.mock('../../../../legacy/server/logging'); -import { LogLevel } from '../../logging/log_level'; +import { LogLevel } from '../../logging'; import { LegacyLoggingServer } from './legacy_logging_server'; test('correctly forwards log records.', () => { diff --git a/src/core/server/legacy/logging/legacy_logging_server.ts b/src/core/server/legacy/logging/legacy_logging_server.ts index 4a7fea87cf69f..096dbe54565e1 100644 --- a/src/core/server/legacy/logging/legacy_logging_server.ts +++ b/src/core/server/legacy/logging/legacy_logging_server.ts @@ -23,8 +23,7 @@ import Podium from 'podium'; import { Config } from '../../../../legacy/server/config'; // @ts-expect-error: implicit any for JS file import { setupLogging } from '../../../../legacy/server/logging'; -import { LogLevel } from '../../logging/log_level'; -import { LogRecord } from '../../logging/log_record'; +import { LogLevel, LogRecord } from '../../logging'; import { LegacyVars } from '../../types'; export const metadataSymbol = Symbol('log message with metadata'); diff --git a/src/core/server/logging/appenders/appenders.ts b/src/core/server/logging/appenders/appenders.ts index 9c19ee2bd8be5..4e6920c50686c 100644 --- a/src/core/server/logging/appenders/appenders.ts +++ b/src/core/server/logging/appenders/appenders.ts @@ -19,13 +19,13 @@ import { schema } from '@kbn/config-schema'; import { assertNever } from '@kbn/std'; +import { DisposableAppender } from '@kbn/logging'; import { LegacyAppender, LegacyAppenderConfig, } from '../../legacy/logging/appenders/legacy_appender'; import { Layouts } from '../layouts/layouts'; -import { LogRecord } from '../log_record'; import { ConsoleAppender, ConsoleAppenderConfig } from './console/console_appender'; import { FileAppender, FileAppenderConfig } from './file/file_appender'; @@ -44,25 +44,6 @@ export const appendersSchema = schema.oneOf([ /** @public */ export type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig; -/** - * Entity that can append `LogRecord` instances to file, stdout, memory or whatever - * is implemented internally. It's supposed to be used by `Logger`. - * @internal - */ -export interface Appender { - append(record: LogRecord): void; -} - -/** - * This interface should be additionally implemented by the `Appender`'s if they are supposed - * to be properly disposed. It's intentionally separated from `Appender` interface so that `Logger` - * that interacts with `Appender` doesn't have control over appender lifetime. - * @internal - */ -export interface DisposableAppender extends Appender { - dispose: () => void; -} - /** @internal */ export class Appenders { public static configSchema = appendersSchema; diff --git a/src/core/server/logging/appenders/buffer/buffer_appender.test.ts b/src/core/server/logging/appenders/buffer/buffer_appender.test.ts index 49d70db8d5d43..7981aef64e589 100644 --- a/src/core/server/logging/appenders/buffer/buffer_appender.test.ts +++ b/src/core/server/logging/appenders/buffer/buffer_appender.test.ts @@ -17,8 +17,7 @@ * under the License. */ -import { LogLevel } from '../../log_level'; -import { LogRecord } from '../../log_record'; +import { LogLevel, LogRecord } from '@kbn/logging'; import { BufferAppender } from './buffer_appender'; test('`flush()` does not return any record buffered at the beginning.', () => { diff --git a/src/core/server/logging/appenders/buffer/buffer_appender.ts b/src/core/server/logging/appenders/buffer/buffer_appender.ts index 7024d3e5d16df..9e3a9d0f910f8 100644 --- a/src/core/server/logging/appenders/buffer/buffer_appender.ts +++ b/src/core/server/logging/appenders/buffer/buffer_appender.ts @@ -17,8 +17,7 @@ * under the License. */ -import { LogRecord } from '../../log_record'; -import { DisposableAppender } from '../appenders'; +import { LogRecord, DisposableAppender } from '@kbn/logging'; /** * Simple appender that just buffers `LogRecord` instances it receives. It is a *reserved* appender diff --git a/src/core/server/logging/appenders/console/console_appender.test.ts b/src/core/server/logging/appenders/console/console_appender.test.ts index 6e30df1cfb65c..0601ac10167ac 100644 --- a/src/core/server/logging/appenders/console/console_appender.test.ts +++ b/src/core/server/logging/appenders/console/console_appender.test.ts @@ -29,8 +29,7 @@ jest.mock('../../layouts/layouts', () => { }; }); -import { LogLevel } from '../../log_level'; -import { LogRecord } from '../../log_record'; +import { LogRecord, LogLevel } from '@kbn/logging'; import { ConsoleAppender } from './console_appender'; test('`configSchema` creates correct schema.', () => { diff --git a/src/core/server/logging/appenders/console/console_appender.ts b/src/core/server/logging/appenders/console/console_appender.ts index a54674b1d347c..dc491fcff664c 100644 --- a/src/core/server/logging/appenders/console/console_appender.ts +++ b/src/core/server/logging/appenders/console/console_appender.ts @@ -18,10 +18,8 @@ */ import { schema } from '@kbn/config-schema'; - -import { Layout, Layouts, LayoutConfigType } from '../../layouts/layouts'; -import { LogRecord } from '../../log_record'; -import { DisposableAppender } from '../appenders'; +import { Layout, LogRecord, DisposableAppender } from '@kbn/logging'; +import { Layouts, LayoutConfigType } from '../../layouts/layouts'; const { literal, object } = schema; diff --git a/src/core/server/logging/appenders/file/file_appender.test.ts b/src/core/server/logging/appenders/file/file_appender.test.ts index bff60029faf11..645455c5ae04c 100644 --- a/src/core/server/logging/appenders/file/file_appender.test.ts +++ b/src/core/server/logging/appenders/file/file_appender.test.ts @@ -19,8 +19,7 @@ import { mockCreateWriteStream } from './file_appender.test.mocks'; -import { LogLevel } from '../../log_level'; -import { LogRecord } from '../../log_record'; +import { LogRecord, LogLevel } from '@kbn/logging'; import { FileAppender } from './file_appender'; const tickMs = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/core/server/logging/appenders/file/file_appender.ts b/src/core/server/logging/appenders/file/file_appender.ts index a0e484cd87c8f..b1712bd4e9412 100644 --- a/src/core/server/logging/appenders/file/file_appender.ts +++ b/src/core/server/logging/appenders/file/file_appender.ts @@ -18,11 +18,10 @@ */ import { schema } from '@kbn/config-schema'; +import { LogRecord, Layout, DisposableAppender } from '@kbn/logging'; import { createWriteStream, WriteStream } from 'fs'; -import { Layout, Layouts, LayoutConfigType } from '../../layouts/layouts'; -import { LogRecord } from '../../log_record'; -import { DisposableAppender } from '../appenders'; +import { Layouts, LayoutConfigType } from '../../layouts/layouts'; export interface FileAppenderConfig { kind: 'file'; diff --git a/src/core/server/logging/index.ts b/src/core/server/logging/index.ts index 9471972030281..01f153cae9e2d 100644 --- a/src/core/server/logging/index.ts +++ b/src/core/server/logging/index.ts @@ -17,10 +17,17 @@ * under the License. */ -export { Logger, LogMeta } from './logger'; -export { LoggerFactory } from './logger_factory'; -export { LogRecord } from './log_record'; -export { LogLevel } from './log_level'; +export { + DisposableAppender, + Appender, + LogRecord, + Layout, + LoggerFactory, + LogMeta, + Logger, + LogLevelId, + LogLevel, +} from '@kbn/logging'; export { config, LoggingConfigType, diff --git a/src/core/server/logging/layouts/conversions/date.ts b/src/core/server/logging/layouts/conversions/date.ts index d3ed54fb98240..3a43c0ffcd389 100644 --- a/src/core/server/logging/layouts/conversions/date.ts +++ b/src/core/server/logging/layouts/conversions/date.ts @@ -18,9 +18,9 @@ */ import moment from 'moment-timezone'; import { last } from 'lodash'; +import { LogRecord } from '@kbn/logging'; import { Conversion } from './type'; -import { LogRecord } from '../../log_record'; const dateRegExp = /%date({(?[^}]+)})?({(?[^}]+)})?/g; diff --git a/src/core/server/logging/layouts/conversions/level.ts b/src/core/server/logging/layouts/conversions/level.ts index 58b271140eff5..83208242dc258 100644 --- a/src/core/server/logging/layouts/conversions/level.ts +++ b/src/core/server/logging/layouts/conversions/level.ts @@ -18,10 +18,9 @@ */ import chalk from 'chalk'; +import { LogRecord, LogLevel } from '@kbn/logging'; import { Conversion } from './type'; -import { LogLevel } from '../../log_level'; -import { LogRecord } from '../../log_record'; const LEVEL_COLORS = new Map([ [LogLevel.Fatal, chalk.red], diff --git a/src/core/server/logging/layouts/conversions/logger.ts b/src/core/server/logging/layouts/conversions/logger.ts index debb1737ab95a..e63976052443b 100644 --- a/src/core/server/logging/layouts/conversions/logger.ts +++ b/src/core/server/logging/layouts/conversions/logger.ts @@ -18,9 +18,9 @@ */ import chalk from 'chalk'; +import { LogRecord } from '@kbn/logging'; import { Conversion } from './type'; -import { LogRecord } from '../../log_record'; export const LoggerConversion: Conversion = { pattern: /%logger/g, diff --git a/src/core/server/logging/layouts/conversions/message.ts b/src/core/server/logging/layouts/conversions/message.ts index f8c5e68ada4fb..73d85532a5a90 100644 --- a/src/core/server/logging/layouts/conversions/message.ts +++ b/src/core/server/logging/layouts/conversions/message.ts @@ -17,8 +17,8 @@ * under the License. */ +import { LogRecord } from '@kbn/logging'; import { Conversion } from './type'; -import { LogRecord } from '../../log_record'; export const MessageConversion: Conversion = { pattern: /%message/g, diff --git a/src/core/server/logging/layouts/conversions/meta.ts b/src/core/server/logging/layouts/conversions/meta.ts index ee8c207389fbe..b78db41b7e969 100644 --- a/src/core/server/logging/layouts/conversions/meta.ts +++ b/src/core/server/logging/layouts/conversions/meta.ts @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ + +import { LogRecord } from '@kbn/logging'; import { Conversion } from './type'; -import { LogRecord } from '../../log_record'; export const MetaConversion: Conversion = { pattern: /%meta/g, diff --git a/src/core/server/logging/layouts/conversions/pid.ts b/src/core/server/logging/layouts/conversions/pid.ts index 37d34a4f1cf8b..f6902005f5668 100644 --- a/src/core/server/logging/layouts/conversions/pid.ts +++ b/src/core/server/logging/layouts/conversions/pid.ts @@ -17,8 +17,8 @@ * under the License. */ +import { LogRecord } from '@kbn/logging'; import { Conversion } from './type'; -import { LogRecord } from '../../log_record'; export const PidConversion: Conversion = { pattern: /%pid/g, diff --git a/src/core/server/logging/layouts/conversions/type.ts b/src/core/server/logging/layouts/conversions/type.ts index a57a1f954e53a..be172a0a98f7d 100644 --- a/src/core/server/logging/layouts/conversions/type.ts +++ b/src/core/server/logging/layouts/conversions/type.ts @@ -16,7 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { LogRecord } from 'kibana/server'; + +import { LogRecord } from '@kbn/logging'; export interface Conversion { pattern: RegExp; diff --git a/src/core/server/logging/layouts/json_layout.test.ts b/src/core/server/logging/layouts/json_layout.test.ts index 6cda1e4806aa8..f476e3f217278 100644 --- a/src/core/server/logging/layouts/json_layout.test.ts +++ b/src/core/server/logging/layouts/json_layout.test.ts @@ -17,8 +17,7 @@ * under the License. */ -import { LogLevel } from '../log_level'; -import { LogRecord } from '../log_record'; +import { LogLevel, LogRecord } from '@kbn/logging'; import { JsonLayout } from './json_layout'; const timestamp = new Date(Date.UTC(2012, 1, 1, 14, 30, 22, 11)); diff --git a/src/core/server/logging/layouts/json_layout.ts b/src/core/server/logging/layouts/json_layout.ts index 37eb6b8c4806e..7573d0b837416 100644 --- a/src/core/server/logging/layouts/json_layout.ts +++ b/src/core/server/logging/layouts/json_layout.ts @@ -20,9 +20,7 @@ import moment from 'moment-timezone'; import { merge } from 'lodash'; import { schema } from '@kbn/config-schema'; - -import { LogRecord } from '../log_record'; -import { Layout } from './layouts'; +import { LogRecord, Layout } from '@kbn/logging'; const { literal, object } = schema; diff --git a/src/core/server/logging/layouts/layouts.ts b/src/core/server/logging/layouts/layouts.ts index 03e8adbee6311..faf05e4d1e347 100644 --- a/src/core/server/logging/layouts/layouts.ts +++ b/src/core/server/logging/layouts/layouts.ts @@ -18,9 +18,9 @@ */ import { schema } from '@kbn/config-schema'; +import { Layout } from '@kbn/logging'; import { assertNever } from '@kbn/std'; -import { LogRecord } from '../log_record'; import { JsonLayout, JsonLayoutConfigType } from './json_layout'; import { PatternLayout, PatternLayoutConfigType } from './pattern_layout'; @@ -28,14 +28,6 @@ const { oneOf } = schema; export type LayoutConfigType = PatternLayoutConfigType | JsonLayoutConfigType; -/** - * Entity that can format `LogRecord` instance into a string. - * @internal - */ -export interface Layout { - format(record: LogRecord): string; -} - /** @internal */ export class Layouts { public static configSchema = oneOf([JsonLayout.configSchema, PatternLayout.configSchema]); diff --git a/src/core/server/logging/layouts/pattern_layout.test.ts b/src/core/server/logging/layouts/pattern_layout.test.ts index cce55b147e0ed..d37ee3c87d08f 100644 --- a/src/core/server/logging/layouts/pattern_layout.test.ts +++ b/src/core/server/logging/layouts/pattern_layout.test.ts @@ -17,9 +17,8 @@ * under the License. */ +import { LogLevel, LogRecord } from '@kbn/logging'; import { stripAnsiSnapshotSerializer } from '../../../test_helpers/strip_ansi_snapshot_serializer'; -import { LogLevel } from '../log_level'; -import { LogRecord } from '../log_record'; import { PatternLayout, patternSchema } from './pattern_layout'; const timestamp = new Date(Date.UTC(2012, 1, 1, 14, 30, 22, 11)); diff --git a/src/core/server/logging/layouts/pattern_layout.ts b/src/core/server/logging/layouts/pattern_layout.ts index 5dfc8aca77f18..2ca444f54b499 100644 --- a/src/core/server/logging/layouts/pattern_layout.ts +++ b/src/core/server/logging/layouts/pattern_layout.ts @@ -18,9 +18,8 @@ */ import { schema } from '@kbn/config-schema'; +import { LogRecord, Layout } from '@kbn/logging'; -import { LogRecord } from '../log_record'; -import { Layout } from './layouts'; import { Conversion, LoggerConversion, diff --git a/src/core/server/logging/logger.mock.ts b/src/core/server/logging/logger.mock.ts index a3bb07ea4c095..5af967ecd430e 100644 --- a/src/core/server/logging/logger.mock.ts +++ b/src/core/server/logging/logger.mock.ts @@ -17,30 +17,4 @@ * under the License. */ -import { Logger } from './logger'; - -export type MockedLogger = jest.Mocked & { context: string[] }; - -const createLoggerMock = (context: string[] = []) => { - const mockLog: MockedLogger = { - context, - debug: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - info: jest.fn(), - log: jest.fn(), - trace: jest.fn(), - warn: jest.fn(), - get: jest.fn(), - }; - mockLog.get.mockImplementation((...ctx) => ({ - ctx, - ...mockLog, - })); - - return mockLog; -}; - -export const loggerMock = { - create: createLoggerMock, -}; +export { loggerMock, MockedLogger } from '@kbn/logging/target/mocks'; diff --git a/src/core/server/logging/logger.test.ts b/src/core/server/logging/logger.test.ts index 1cc00a254300b..1796519ff65e5 100644 --- a/src/core/server/logging/logger.test.ts +++ b/src/core/server/logging/logger.test.ts @@ -17,9 +17,8 @@ * under the License. */ +import { LogLevel, Appender } from '@kbn/logging'; import { LoggingConfig } from './logging_config'; -import { Appender } from './appenders/appenders'; -import { LogLevel } from './log_level'; import { BaseLogger } from './logger'; const context = LoggingConfig.getLoggerContext(['context', 'parent', 'child']); diff --git a/src/core/server/logging/logger.ts b/src/core/server/logging/logger.ts index 285998c23832c..6861072ef3b8b 100644 --- a/src/core/server/logging/logger.ts +++ b/src/core/server/logging/logger.ts @@ -17,86 +17,7 @@ * under the License. */ -import { Appender } from './appenders/appenders'; -import { LogLevel } from './log_level'; -import { LogRecord } from './log_record'; -import { LoggerFactory } from './logger_factory'; - -/** - * Contextual metadata - * - * @public - */ -export interface LogMeta { - [key: string]: any; -} - -/** - * Logger exposes all the necessary methods to log any type of information and - * this is the interface used by the logging consumers including plugins. - * - * @public - */ -export interface Logger { - /** - * Log messages at the most detailed log level - * - * @param message - The log message - * @param meta - - */ - trace(message: string, meta?: LogMeta): void; - - /** - * Log messages useful for debugging and interactive investigation - * @param message - The log message - * @param meta - - */ - debug(message: string, meta?: LogMeta): void; - - /** - * Logs messages related to general application flow - * @param message - The log message - * @param meta - - */ - info(message: string, meta?: LogMeta): void; - - /** - * Logs abnormal or unexpected errors or messages - * @param errorOrMessage - An Error object or message string to log - * @param meta - - */ - warn(errorOrMessage: string | Error, meta?: LogMeta): void; - - /** - * Logs abnormal or unexpected errors or messages that caused a failure in the application flow - * - * @param errorOrMessage - An Error object or message string to log - * @param meta - - */ - error(errorOrMessage: string | Error, meta?: LogMeta): void; - - /** - * Logs abnormal or unexpected errors or messages that caused an unrecoverable failure - * - * @param errorOrMessage - An Error object or message string to log - * @param meta - - */ - fatal(errorOrMessage: string | Error, meta?: LogMeta): void; - - /** @internal */ - log(record: LogRecord): void; - - /** - * Returns a new {@link Logger} instance extending the current logger context. - * - * @example - * ```typescript - * const logger = loggerFactory.get('plugin', 'service'); // 'plugin.service' context - * const subLogger = logger.get('feature'); // 'plugin.service.feature' context - * ``` - */ - get(...childContextPaths: string[]): Logger; -} +import { Appender, LogLevel, LogRecord, LoggerFactory, LogMeta, Logger } from '@kbn/logging'; function isError(x: any): x is Error { return x instanceof Error; diff --git a/src/core/server/logging/logger_adapter.ts b/src/core/server/logging/logger_adapter.ts index 14e5712e55c58..4ce65bf3302e7 100644 --- a/src/core/server/logging/logger_adapter.ts +++ b/src/core/server/logging/logger_adapter.ts @@ -17,8 +17,7 @@ * under the License. */ -import { LogRecord } from './log_record'; -import { Logger, LogMeta } from './logger'; +import { LogRecord, Logger, LogMeta } from '@kbn/logging'; /** @internal */ export class LoggerAdapter implements Logger { diff --git a/src/core/server/logging/logging_service.ts b/src/core/server/logging/logging_service.ts index 09051f8f07702..f2b609f2258c7 100644 --- a/src/core/server/logging/logging_service.ts +++ b/src/core/server/logging/logging_service.ts @@ -18,10 +18,10 @@ */ import { Observable, Subscription } from 'rxjs'; +import { Logger } from '@kbn/logging'; import { CoreService } from '../../types'; import { LoggingConfig, LoggerContextConfigInput } from './logging_config'; import { ILoggingSystem } from './logging_system'; -import { Logger } from './logger'; import { CoreContext } from '../core_context'; /** diff --git a/src/core/server/logging/logging_system.mock.ts b/src/core/server/logging/logging_system.mock.ts index ac1e9b5196002..6ea784be5411f 100644 --- a/src/core/server/logging/logging_system.mock.ts +++ b/src/core/server/logging/logging_system.mock.ts @@ -18,8 +18,8 @@ */ // Test helpers to simplify mocking logs and collecting all their outputs +import { LoggerFactory } from '@kbn/logging'; import { ILoggingSystem } from './logging_system'; -import { LoggerFactory } from './logger_factory'; import { loggerMock, MockedLogger } from './logger.mock'; const createLoggingSystemMock = () => { @@ -48,15 +48,7 @@ const createLoggingSystemMock = () => { 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, - }; + return loggerMock.collect(mockLog); }; const clearLoggingSystemMock = (loggerFactory: LoggerFactory) => { @@ -67,13 +59,7 @@ const clearLoggingSystemMock = (loggerFactory: LoggerFactory) => { 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(); + loggerMock.clear(mockLog); }; export const loggingSystemMock = { diff --git a/src/core/server/logging/logging_system.ts b/src/core/server/logging/logging_system.ts index a3970b1720950..8bc22bdf537af 100644 --- a/src/core/server/logging/logging_system.ts +++ b/src/core/server/logging/logging_system.ts @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ + import type { PublicMethodsOf } from '@kbn/utility-types'; -import { Appenders, DisposableAppender } from './appenders/appenders'; +import { DisposableAppender, LogLevel, Logger, LoggerFactory } from '@kbn/logging'; +import { Appenders } from './appenders/appenders'; import { BufferAppender } from './appenders/buffer/buffer_appender'; -import { LogLevel } from './log_level'; -import { BaseLogger, Logger } from './logger'; +import { BaseLogger } from './logger'; import { LoggerAdapter } from './logger_adapter'; -import { LoggerFactory } from './logger_factory'; import { LoggingConfigType, LoggerConfigType, diff --git a/src/core/server/metrics/metrics_service.test.ts b/src/core/server/metrics/metrics_service.test.ts index f2019de7b6cab..269931d0e33ad 100644 --- a/src/core/server/metrics/metrics_service.test.ts +++ b/src/core/server/metrics/metrics_service.test.ts @@ -18,10 +18,11 @@ */ import moment from 'moment'; + +import { configServiceMock } from '../config/mocks'; import { mockOpsCollector } from './metrics_service.test.mocks'; import { MetricsService } from './metrics_service'; import { mockCoreContext } from '../core_context.mock'; -import { configServiceMock } from '../config/config_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { take } from 'rxjs/operators'; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 3c79706422cd4..5d6bf41fec3f3 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -37,10 +37,10 @@ import { environmentServiceMock } from './environment/environment_service.mock'; import { statusServiceMock } from './status/status_service.mock'; import { auditTrailServiceMock } from './audit_trail/audit_trail_service.mock'; +export { configServiceMock } from './config/mocks'; export { httpServerMock } from './http/http_server.mocks'; export { httpResourcesMock } from './http_resources/http_resources_service.mock'; 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 { loggingSystemMock } from './logging/logging_system.mock'; diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.mocks.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.test.mocks.ts index eb8dc1e609582..3634a7ed2c277 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.test.mocks.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.test.mocks.ts @@ -17,6 +17,12 @@ * under the License. */ +const realFs = jest.requireActual('fs'); + export const mockReadFile = jest.fn(); const mockStat = jest.fn(); -jest.mock('fs', () => ({ readFile: mockReadFile, stat: mockStat })); +jest.doMock('fs', () => ({ + ...realFs, + readFile: mockReadFile, + stat: mockStat, +})); 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 836aabf881474..cb9f5982d0f19 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts @@ -17,9 +17,9 @@ * under the License. */ -import { PluginDiscoveryErrorType } from './plugin_discovery_error'; - import { mockReadFile } from './plugin_manifest_parser.test.mocks'; + +import { PluginDiscoveryErrorType } from './plugin_discovery_error'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { resolve } from 'path'; diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts b/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts index 83accc06cb995..131ee58e21a81 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts @@ -17,5 +17,10 @@ * under the License. */ -export const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] }); -jest.mock('../../../../../package.json', () => mockPackage); +export const mockPackage = { + raw: {}, +}; + +jest.doMock('load-json-file', () => ({ + sync: () => mockPackage.raw, +})); diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.ts b/src/core/server/plugins/discovery/plugins_discovery.test.ts index 4894f19e38df4..0dfc5599e8007 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.ts @@ -17,18 +17,19 @@ * under the License. */ +// must be before mocks imports to avoid conflicting with `REPO_ROOT` accessor. +import { REPO_ROOT } from '@kbn/dev-utils'; import { mockPackage } from './plugins_discovery.test.mocks'; import mockFs from 'mock-fs'; import { loggingSystemMock } from '../../logging/logging_system.mock'; +import { getEnvOptions, rawConfigServiceMock } from '../../config/mocks'; import { first, map, toArray } from 'rxjs/operators'; import { resolve } from 'path'; import { ConfigService, Env } from '../../config'; -import { getEnvOptions } from '../../config/__mocks__/env'; import { PluginsConfig, PluginsConfigType, config } from '../plugins_config'; import type { InstanceInfo } from '../plugin_context'; import { discover } from './plugins_discovery'; -import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; import { CoreContext } from '../../core_context'; const KIBANA_ROOT = process.cwd(); @@ -94,6 +95,7 @@ describe('plugins discovery system', () => { }; env = Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { envName: 'development' }, }) @@ -382,6 +384,7 @@ describe('plugins discovery system', () => { const extraPluginTestPath = resolve(process.cwd(), 'my-extra-plugin'); env = Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { dev: false, envName: 'development' }, }) @@ -409,6 +412,7 @@ describe('plugins discovery system', () => { const extraPluginTestPath = resolve(process.cwd(), 'my-extra-plugin'); env = Env.createDefault( + REPO_ROOT, getEnvOptions({ cliArgs: { dev: false, envName: 'production' }, }) diff --git a/src/core/server/plugins/integration_tests/plugins_service.test.mocks.ts b/src/core/server/plugins/integration_tests/plugins_service.test.mocks.ts index d81a7eb5db4ae..ed73fe9b99be6 100644 --- a/src/core/server/plugins/integration_tests/plugins_service.test.mocks.ts +++ b/src/core/server/plugins/integration_tests/plugins_service.test.mocks.ts @@ -17,11 +17,13 @@ * under the License. */ -export const mockPackage = new Proxy( - { raw: { __dirname: '/tmp' } as any }, - { get: (obj, prop) => obj.raw[prop] } -); -jest.mock('../../../../core/server/utils/package_json', () => ({ pkg: mockPackage })); +export const mockPackage = { + raw: { __dirname: '/tmp' } as any, +}; + +jest.doMock('load-json-file', () => ({ + sync: () => mockPackage.raw, +})); export const mockDiscover = jest.fn(); jest.mock('../discovery/plugins_discovery', () => ({ discover: mockDiscover })); 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 5a216b75a83b9..93fee05016ff2 100644 --- a/src/core/server/plugins/integration_tests/plugins_service.test.ts +++ b/src/core/server/plugins/integration_tests/plugins_service.test.ts @@ -17,15 +17,16 @@ * under the License. */ +// must be before mocks imports to avoid conflicting with `REPO_ROOT` accessor. +import { REPO_ROOT } from '@kbn/dev-utils'; import { mockPackage, mockDiscover } from './plugins_service.test.mocks'; import { join } from 'path'; import { PluginsService } from '../plugins_service'; import { ConfigPath, ConfigService, Env } from '../../config'; -import { getEnvOptions } from '../../config/__mocks__/env'; +import { getEnvOptions, rawConfigServiceMock } from '../../config/mocks'; import { BehaviorSubject, from } from 'rxjs'; -import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; import { config } from '../plugins_config'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { environmentServiceMock } from '../../environment/environment_service.mock'; @@ -93,7 +94,7 @@ describe('PluginsService', () => { }, }; - const env = Env.createDefault(getEnvOptions()); + const env = Env.createDefault(REPO_ROOT, getEnvOptions()); const config$ = new BehaviorSubject>({ plugins: { initialize: true, diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 1108ffc248161..ae22ead2aa4be 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -19,14 +19,14 @@ import { join } from 'path'; import { BehaviorSubject } from 'rxjs'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { schema } from '@kbn/config-schema'; import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { coreMock } from '../mocks'; -import { configServiceMock } from '../config/config_service.mock'; import { loggingSystemMock } from '../logging/logging_system.mock'; +import { getEnvOptions, configServiceMock } from '../config/mocks'; import { PluginWrapper } from './plugin'; import { PluginManifest } from './types'; @@ -77,7 +77,7 @@ const setupDeps = coreMock.createInternalSetup(); beforeEach(() => { coreId = Symbol('core'); - env = Env.createDefault(getEnvOptions()); + env = Env.createDefault(REPO_ROOT, getEnvOptions()); instanceInfo = { uuid: 'instance-uuid', }; diff --git a/src/core/server/plugins/plugin_context.test.ts b/src/core/server/plugins/plugin_context.test.ts index 578c5f39d71ea..5ce91c9a623dc 100644 --- a/src/core/server/plugins/plugin_context.test.ts +++ b/src/core/server/plugins/plugin_context.test.ts @@ -19,12 +19,12 @@ import { duration } from 'moment'; import { first } from 'rxjs/operators'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { createPluginInitializerContext, InstanceInfo } from './plugin_context'; import { CoreContext } from '../core_context'; import { Env } from '../config'; import { loggingSystemMock } from '../logging/logging_system.mock'; -import { rawConfigServiceMock } from '../config/raw_config_service.mock'; -import { getEnvOptions } from '../config/__mocks__/env'; +import { rawConfigServiceMock, getEnvOptions } from '../config/mocks'; import { PluginManifest } from './types'; import { Server } from '../server'; import { fromRoot } from '../utils'; @@ -58,7 +58,7 @@ describe('createPluginInitializerContext', () => { instanceInfo = { uuid: 'instance-uuid', }; - env = Env.createDefault(getEnvOptions()); + env = Env.createDefault(REPO_ROOT, getEnvOptions()); const config$ = rawConfigServiceMock.create({ rawConfig: {} }); server = new Server(config$, env, logger); await server.setupCoreConfig(); diff --git a/src/core/server/plugins/plugins_config.test.ts b/src/core/server/plugins/plugins_config.test.ts index 180d6093e0404..2a64e79d19bda 100644 --- a/src/core/server/plugins/plugins_config.test.ts +++ b/src/core/server/plugins/plugins_config.test.ts @@ -17,13 +17,14 @@ * under the License. */ +import { REPO_ROOT } from '@kbn/dev-utils'; +import { getEnvOptions } from '../config/mocks'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; describe('PluginsConfig', () => { it('retrieves additionalPluginPaths from config.paths when in production mode', () => { - const env = Env.createDefault(getEnvOptions({ cliArgs: { dev: false } })); + const env = Env.createDefault(REPO_ROOT, getEnvOptions({ cliArgs: { dev: false } })); const rawConfig: PluginsConfigType = { initialize: true, paths: ['some-path', 'another-path'], @@ -33,7 +34,7 @@ describe('PluginsConfig', () => { }); it('retrieves additionalPluginPaths from config.paths when in development mode', () => { - const env = Env.createDefault(getEnvOptions({ cliArgs: { dev: true } })); + const env = Env.createDefault(REPO_ROOT, getEnvOptions({ cliArgs: { dev: true } })); const rawConfig: PluginsConfigType = { initialize: true, paths: ['some-path', 'another-path'], diff --git a/src/core/server/plugins/plugins_service.test.mocks.ts b/src/core/server/plugins/plugins_service.test.mocks.ts index 8d4ba12c8375c..15e4187ef95ed 100644 --- a/src/core/server/plugins/plugins_service.test.mocks.ts +++ b/src/core/server/plugins/plugins_service.test.mocks.ts @@ -17,11 +17,25 @@ * under the License. */ -export const mockPackage = new Proxy( - { raw: { __dirname: '/tmp' } as any }, - { get: (obj, prop) => obj.raw[prop] } -); -jest.mock('../../../core/server/utils/package_json', () => ({ pkg: mockPackage })); +import { REPO_ROOT } from '@kbn/utils'; +import { resolve } from 'path'; + +const loadJsonFile = jest.requireActual('load-json-file'); +const kibanaPackagePath = resolve(REPO_ROOT, 'package.json'); + +export const mockPackage = { + raw: { __dirname: '/tmp', name: 'kibana' } as any, +}; + +jest.doMock('load-json-file', () => ({ + ...loadJsonFile, + sync: (path: string) => { + if (path === kibanaPackagePath) { + return mockPackage.raw; + } + return loadJsonFile.sync(path); + }, +})); export const mockDiscover = jest.fn(); jest.mock('./discovery/plugins_discovery', () => ({ discover: mockDiscover })); diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 5e613343c302f..d36fd2251176a 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -22,11 +22,10 @@ import { mockDiscover, mockPackage } from './plugins_service.test.mocks'; import { resolve, join } from 'path'; import { BehaviorSubject, from } from 'rxjs'; import { schema } from '@kbn/config-schema'; -import { createAbsolutePathSerializer } from '@kbn/dev-utils'; +import { createAbsolutePathSerializer, REPO_ROOT } from '@kbn/dev-utils'; import { ConfigPath, ConfigService, Env } from '../config'; -import { rawConfigServiceMock } from '../config/raw_config_service.mock'; -import { getEnvOptions } from '../config/__mocks__/env'; +import { rawConfigServiceMock, getEnvOptions } from '../config/mocks'; import { coreMock } from '../mocks'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { environmentServiceMock } from '../environment/environment_service.mock'; @@ -116,7 +115,7 @@ describe('PluginsService', () => { }; coreId = Symbol('core'); - env = Env.createDefault(getEnvOptions()); + env = Env.createDefault(REPO_ROOT, getEnvOptions()); config$ = new BehaviorSubject>({ plugins: { initialize: true } }); const rawConfigService = rawConfigServiceMock.create({ rawConfig$: config$ }); diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 71ac31db13f92..ae9267ca5cf60 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -24,10 +24,10 @@ import { import { BehaviorSubject } from 'rxjs'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; +import { configServiceMock, getEnvOptions } from '../config/mocks'; import { CoreContext } from '../core_context'; -import { configServiceMock } from '../config/config_service.mock'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { PluginWrapper } from './plugin'; @@ -74,7 +74,7 @@ const setupDeps = coreMock.createInternalSetup(); const startDeps = coreMock.createInternalStart(); beforeEach(() => { - env = Env.createDefault(getEnvOptions()); + env = Env.createDefault(REPO_ROOT, getEnvOptions()); coreContext = { coreId: Symbol(), env, logger, configService: configService as any }; diff --git a/src/core/server/root/index.test.mocks.ts b/src/core/server/root/index.test.mocks.ts index ef4a40fa3db2d..d81b1575adb88 100644 --- a/src/core/server/root/index.test.mocks.ts +++ b/src/core/server/root/index.test.mocks.ts @@ -23,15 +23,14 @@ jest.doMock('../logging/logging_system', () => ({ LoggingSystem: jest.fn(() => logger), })); -import { configServiceMock } from '../config/config_service.mock'; -export const configService = configServiceMock.create(); -jest.doMock('../config/config_service', () => ({ - ConfigService: jest.fn(() => configService), -})); +const realKbnConfig = jest.requireActual('@kbn/config'); -import { rawConfigServiceMock } from '../config/raw_config_service.mock'; +import { configServiceMock, rawConfigServiceMock } from '../config/mocks'; +export const configService = configServiceMock.create(); export const rawConfigService = rawConfigServiceMock.create(); -jest.doMock('../config/raw_config_service', () => ({ +jest.doMock('@kbn/config', () => ({ + ...realKbnConfig, + ConfigService: jest.fn(() => configService), RawConfigService: jest.fn(() => rawConfigService), })); diff --git a/src/core/server/root/index.test.ts b/src/core/server/root/index.test.ts index 5b853903ea4be..4d3fe24c7ba83 100644 --- a/src/core/server/root/index.test.ts +++ b/src/core/server/root/index.test.ts @@ -21,11 +21,12 @@ import { rawConfigService, configService, logger, mockServer } from './index.tes import { BehaviorSubject } from 'rxjs'; import { filter, first } from 'rxjs/operators'; +import { REPO_ROOT } from '@kbn/dev-utils'; +import { getEnvOptions } from '../config/mocks'; import { Root } from '.'; import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; -const env = new Env('.', getEnvOptions()); +const env = Env.createDefault(REPO_ROOT, getEnvOptions()); let mockConsoleError: jest.SpyInstance; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 05eb1b8d42386..ab26f29dce3af 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -21,6 +21,7 @@ import { CatSnapshotsParams } from 'elasticsearch'; import { CatTasksParams } from 'elasticsearch'; import { CatThreadPoolParams } from 'elasticsearch'; import { ClearScrollParams } from 'elasticsearch'; +import { CliArgs } from '@kbn/config'; import { Client } from 'elasticsearch'; import { ClientOptions } from '@elastic/elasticsearch'; import { ClusterAllocationExplainParams } from 'elasticsearch'; @@ -31,7 +32,13 @@ import { ClusterPutSettingsParams } from 'elasticsearch'; import { ClusterRerouteParams } from 'elasticsearch'; import { ClusterStateParams } from 'elasticsearch'; import { ClusterStatsParams } from 'elasticsearch'; +import { ConfigDeprecation } from '@kbn/config'; +import { ConfigDeprecationFactory } from '@kbn/config'; +import { ConfigDeprecationLogger } from '@kbn/config'; +import { ConfigDeprecationProvider } from '@kbn/config'; import { ConfigOptions } from 'elasticsearch'; +import { ConfigPath } from '@kbn/config'; +import { ConfigService } from '@kbn/config'; import { CountParams } from 'elasticsearch'; import { CreateDocumentParams } from 'elasticsearch'; import { DeleteDocumentByQueryParams } from 'elasticsearch'; @@ -40,6 +47,7 @@ import { DeleteScriptParams } from 'elasticsearch'; import { DeleteTemplateParams } from 'elasticsearch'; import { DetailedPeerCertificate } from 'tls'; import { Duration } from 'moment'; +import { EnvironmentMode } from '@kbn/config'; import { ExistsParams } from 'elasticsearch'; import { ExplainParams } from 'elasticsearch'; import { FieldStatsParams } from 'elasticsearch'; @@ -94,6 +102,11 @@ import { IngestPutPipelineParams } from 'elasticsearch'; import { IngestSimulateParams } from 'elasticsearch'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType } from 'src/core/server/kibana_config'; +import { Logger } from '@kbn/logging'; +import { LoggerFactory } from '@kbn/logging'; +import { LogLevel } from '@kbn/logging'; +import { LogMeta } from '@kbn/logging'; +import { LogRecord } from '@kbn/logging'; import { MGetParams } from 'elasticsearch'; import { MGetResponse } from 'elasticsearch'; import { MSearchParams } from 'elasticsearch'; @@ -105,6 +118,7 @@ import { NodesInfoParams } from 'elasticsearch'; import { NodesStatsParams } from 'elasticsearch'; import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; +import { PackageInfo } from '@kbn/config'; import { PathConfigType } from '@kbn/utils'; import { PeerCertificate } from 'tls'; import { PingParams } from 'elasticsearch'; @@ -363,45 +377,17 @@ export const config: { }; }; -// @public -export type ConfigDeprecation = (config: Record, fromPath: string, logger: ConfigDeprecationLogger) => Record; +export { ConfigDeprecation } -// @public -export interface ConfigDeprecationFactory { - rename(oldKey: string, newKey: string): ConfigDeprecation; - renameFromRoot(oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation; - unused(unusedKey: string): ConfigDeprecation; - unusedFromRoot(unusedKey: string): ConfigDeprecation; -} +export { ConfigDeprecationFactory } -// @public -export type ConfigDeprecationLogger = (message: string) => void; +export { ConfigDeprecationLogger } -// @public -export type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => ConfigDeprecation[]; +export { ConfigDeprecationProvider } -// @public (undocumented) -export type ConfigPath = string | string[]; +export { ConfigPath } -// @internal (undocumented) -export class ConfigService { - // Warning: (ae-forgotten-export) The symbol "RawConfigurationProvider" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "Env" needs to be exported by the entry point index.d.ts - constructor(rawConfigProvider: RawConfigurationProvider, env: Env, logger: LoggerFactory); - addDeprecationProvider(path: ConfigPath, provider: ConfigDeprecationProvider): void; - atPath(path: ConfigPath): Observable; - // Warning: (ae-forgotten-export) The symbol "Config" needs to be exported by the entry point index.d.ts - getConfig$(): Observable; - // (undocumented) - getUnusedPaths(): Promise; - // (undocumented) - getUsedPaths(): Promise; - // (undocumented) - isEnabledAtPath(path: ConfigPath): Promise; - optionalAtPath(path: ConfigPath): Observable; - setSchema(path: ConfigPath, schema: Type): Promise; - validate(): Promise; - } +export { ConfigService } // @public export interface ContextSetup { @@ -673,15 +659,7 @@ export interface ElasticsearchStatusMeta { warningNodes: NodesVersionCompatibility['warningNodes']; } -// @public (undocumented) -export interface EnvironmentMode { - // (undocumented) - dev: boolean; - // (undocumented) - name: 'development' | 'production'; - // (undocumented) - prod: boolean; -} +export { EnvironmentMode } // @public export interface ErrorHttpResponseOptions { @@ -1419,18 +1397,7 @@ export interface LegacyUiExports { // @public export type LifecycleResponseFactory = typeof lifecycleResponseFactory; -// @public -export interface Logger { - debug(message: string, meta?: LogMeta): void; - error(errorOrMessage: string | Error, meta?: LogMeta): void; - fatal(errorOrMessage: string | Error, meta?: LogMeta): void; - get(...childContextPaths: string[]): Logger; - info(message: string, meta?: LogMeta): void; - // @internal (undocumented) - log(record: LogRecord): void; - trace(message: string, meta?: LogMeta): void; - warn(errorOrMessage: string | Error, meta?: LogMeta): void; -} +export { Logger } // Warning: (ae-forgotten-export) The symbol "loggerSchema" needs to be exported by the entry point index.d.ts // @@ -1445,69 +1412,18 @@ export interface LoggerContextConfigInput { loggers?: LoggerConfigType[]; } -// @public -export interface LoggerFactory { - get(...contextParts: string[]): Logger; -} +export { LoggerFactory } // @public export interface LoggingServiceSetup { configure(config$: Observable): void; } -// @internal -export class LogLevel { - // (undocumented) - static readonly All: LogLevel; - // (undocumented) - static readonly Debug: LogLevel; - // (undocumented) - static readonly Error: LogLevel; - // (undocumented) - static readonly Fatal: LogLevel; - static fromId(level: LogLevelId): LogLevel; - // Warning: (ae-forgotten-export) The symbol "LogLevelId" needs to be exported by the entry point index.d.ts - // - // (undocumented) - readonly id: LogLevelId; - // (undocumented) - static readonly Info: LogLevel; - // (undocumented) - static readonly Off: LogLevel; - supports(level: LogLevel): boolean; - // (undocumented) - static readonly Trace: LogLevel; - // (undocumented) - readonly value: number; - // (undocumented) - static readonly Warn: LogLevel; -} +export { LogLevel } -// @public -export interface LogMeta { - // (undocumented) - [key: string]: any; -} +export { LogMeta } -// @internal -export interface LogRecord { - // (undocumented) - context: string; - // (undocumented) - error?: Error; - // (undocumented) - level: LogLevel; - // (undocumented) - message: string; - // (undocumented) - meta?: { - [name: string]: any; - }; - // (undocumented) - pid: number; - // (undocumented) - timestamp: Date; -} +export { LogRecord } // @public export interface MetricsServiceSetup { @@ -1666,19 +1582,7 @@ export interface OpsServerMetrics { }; } -// @public (undocumented) -export interface PackageInfo { - // (undocumented) - branch: string; - // (undocumented) - buildNum: number; - // (undocumented) - buildSha: string; - // (undocumented) - dist: boolean; - // (undocumented) - version: string; -} +export { PackageInfo } // @public export interface Plugin { @@ -1692,6 +1596,7 @@ export interface Plugin { + // Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported deprecations?: ConfigDeprecationProvider; exposeToBrowser?: { [P in keyof T]?: boolean; @@ -1729,6 +1634,7 @@ export interface PluginInitializerContext { // @public export interface PluginManifest { + // Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported readonly configPath: ConfigPath; // @deprecated readonly extraPublicDirs?: string[]; diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index 471e482a20e96..77f2787b75412 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -41,9 +41,12 @@ jest.mock('./legacy/legacy_service', () => ({ LegacyService: jest.fn(() => mockLegacyService), })); -import { configServiceMock } from './config/config_service.mock'; +const realKbnConfig = jest.requireActual('@kbn/config'); + +import { configServiceMock } from './config/mocks'; export const mockConfigService = configServiceMock.create(); -jest.doMock('./config/config_service', () => ({ +jest.doMock('@kbn/config', () => ({ + ...realKbnConfig, ConfigService: jest.fn(() => mockConfigService), })); diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 8bf16d9130ef5..3258840d09df2 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -35,14 +35,14 @@ import { } from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; +import { REPO_ROOT } from '@kbn/dev-utils'; +import { rawConfigServiceMock, getEnvOptions } from './config/mocks'; import { Env } from './config'; import { Server } from './server'; -import { getEnvOptions } from './config/__mocks__/env'; import { loggingSystemMock } from './logging/logging_system.mock'; -import { rawConfigServiceMock } from './config/raw_config_service.mock'; -const env = new Env('.', getEnvOptions()); +const env = Env.createDefault(REPO_ROOT, getEnvOptions()); const logger = loggingSystemMock.create(); const rawConfigService = rawConfigServiceMock.create({}); diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 2433aad1a2be5..f8d2f635671fa 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -22,4 +22,4 @@ export { PluginOpaqueId } from './plugins/types'; export * from './saved_objects/types'; export * from './ui_settings/types'; export * from './legacy/types'; -export { EnvironmentMode, PackageInfo } from './config/types'; +export type { EnvironmentMode, PackageInfo } from '@kbn/config'; diff --git a/src/core/test_helpers/kbn_server.ts b/src/core/test_helpers/kbn_server.ts index 488c4b919d3e4..93a173cdbdece 100644 --- a/src/core/test_helpers/kbn_server.ts +++ b/src/core/test_helpers/kbn_server.ts @@ -17,7 +17,7 @@ * under the License. */ import { Client } from 'elasticsearch'; -import { ToolingLog } from '@kbn/dev-utils'; +import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; import { createLegacyEsTestCluster, DEFAULT_SUPERUSER_PASS, @@ -66,7 +66,7 @@ export function createRootWithSettings( settings: Record, cliArgs: Partial = {} ) { - const env = Env.createDefault({ + const env = Env.createDefault(REPO_ROOT, { configs: [], cliArgs: { dev: false, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index a8fbbcb08d358..7ce53a219fb44 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -17,6 +17,7 @@ import { CoreSetup as CoreSetup_2 } from 'kibana/public'; import { CoreStart } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'src/core/public'; import { Ensure } from '@kbn/utility-types'; +import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; @@ -38,12 +39,15 @@ import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType } from 'src/core/server/kibana_config'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; +import { Logger } from '@kbn/logging'; +import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; import { METRIC_TYPE } from '@kbn/analytics'; import { Moment } from 'moment'; import moment from 'moment'; import { NameList } from 'elasticsearch'; import { Observable } from 'rxjs'; +import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index f5b1214185f53..14f176176f647 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -32,6 +32,7 @@ import { ClusterPutSettingsParams } from 'elasticsearch'; import { ClusterRerouteParams } from 'elasticsearch'; import { ClusterStateParams } from 'elasticsearch'; import { ClusterStatsParams } from 'elasticsearch'; +import { ConfigDeprecationProvider } from '@kbn/config'; import { CoreSetup } from 'src/core/server'; import { CoreSetup as CoreSetup_2 } from 'kibana/server'; import { CoreStart } from 'src/core/server'; @@ -43,6 +44,7 @@ import { DeleteScriptParams } from 'elasticsearch'; import { DeleteTemplateParams } from 'elasticsearch'; import { Duration } from 'moment'; import { Ensure } from '@kbn/utility-types'; +import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; import { ExistsParams } from 'elasticsearch'; import { ExplainParams } from 'elasticsearch'; @@ -104,7 +106,10 @@ import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config'; import { KibanaRequest } from 'kibana/server'; import { LegacyAPICaller as LegacyAPICaller_2 } from 'kibana/server'; +import { Logger } from '@kbn/logging'; import { Logger as Logger_2 } from 'kibana/server'; +import { LoggerFactory } from '@kbn/logging'; +import { LogMeta } from '@kbn/logging'; import { MGetParams } from 'elasticsearch'; import { MGetResponse } from 'elasticsearch'; import { Moment } from 'moment'; @@ -117,6 +122,7 @@ import { NodesHotThreadsParams } from 'elasticsearch'; import { NodesInfoParams } from 'elasticsearch'; import { NodesStatsParams } from 'elasticsearch'; import { Observable } from 'rxjs'; +import { PackageInfo } from '@kbn/config'; import { PathConfigType } from '@kbn/utils'; import { PingParams } from 'elasticsearch'; import { Plugin as Plugin_2 } from 'src/core/server'; diff --git a/yarn.lock b/yarn.lock index ba793307e1e97..8bdb06152a7f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5708,6 +5708,11 @@ ansi-escapes@^1.0.0, ansi-escapes@^1.1.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= +ansi-escapes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" + integrity sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs= + ansi-escapes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" @@ -8048,6 +8053,15 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" +camelcase-keys@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" + integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c= + dependencies: + camelcase "^4.1.0" + map-obj "^2.0.0" + quick-lru "^1.0.0" + camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" @@ -8072,7 +8086,7 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= -camelcase@^4.0.0: +camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= @@ -10336,7 +10350,7 @@ debuglog@^1.0.1: resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -decamelize-keys@^1.1.0: +decamelize-keys@^1.0.0, decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= @@ -11856,6 +11870,17 @@ eslint-config-prettier@^6.11.0: dependencies: get-stdin "^6.0.0" +eslint-formatter-pretty@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-formatter-pretty/-/eslint-formatter-pretty-1.3.0.tgz#985d9e41c1f8475f4a090c5dbd2dfcf2821d607e" + integrity sha512-5DY64Y1rYCm7cfFDHEGUn54bvCnK+wSUVF07N8oXeqUJFSd+gnYOTXbzelQ1HurESluY6gnEQPmXOIkB4Wa+gA== + dependencies: + ansi-escapes "^2.0.0" + chalk "^2.1.0" + log-symbols "^2.0.0" + plur "^2.1.2" + string-width "^2.0.0" + eslint-formatter-pretty@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-formatter-pretty/-/eslint-formatter-pretty-4.0.0.tgz#dc15f3bf4fb51b7ba5fbedb77f57ba8841140ce2" @@ -14162,7 +14187,7 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globby@^9.2.0: +globby@^9.1.0, globby@^9.2.0: version "9.2.0" resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg== @@ -16174,6 +16199,11 @@ iron@5.x.x: cryptiles "4.x.x" hoek "5.x.x" +irregular-plurals@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766" + integrity sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y= + irregular-plurals@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-3.2.0.tgz#b19c490a0723798db51b235d7e39add44dab0822" @@ -19006,7 +19036,7 @@ log-ok@^0.1.1: ansi-green "^0.1.1" success-symbol "^0.1.0" -log-symbols@2.2.0, log-symbols@^2.1.0, log-symbols@^2.2.0: +log-symbols@2.2.0, log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== @@ -19308,6 +19338,11 @@ map-obj@^1.0.0, map-obj@^1.0.1: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= +map-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" + integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= + map-obj@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5" @@ -19611,6 +19646,21 @@ meow@^3.0.0, meow@^3.3.0, meow@^3.7.0: redent "^1.0.0" trim-newlines "^1.0.0" +meow@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" + integrity sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig== + dependencies: + camelcase-keys "^4.0.0" + decamelize-keys "^1.0.0" + loud-rejection "^1.0.0" + minimist-options "^3.0.1" + normalize-package-data "^2.3.4" + read-pkg-up "^3.0.0" + redent "^2.0.0" + trim-newlines "^2.0.0" + yargs-parser "^10.0.0" + meow@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/meow/-/meow-6.1.1.tgz#1ad64c4b76b2a24dfb2f635fddcadf320d251467" @@ -19874,6 +19924,14 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" +minimist-options@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" + integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + minimist-options@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -22363,6 +22421,13 @@ plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" +plur@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" + integrity sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo= + dependencies: + irregular-plurals "^1.0.0" + plur@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84" @@ -23075,6 +23140,11 @@ queue@6.0.1: dependencies: inherits "~2.0.3" +quick-lru@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" + integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= + quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" @@ -24096,6 +24166,14 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + read-pkg-up@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" @@ -24325,6 +24403,14 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" +redent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" + integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo= + dependencies: + indent-string "^3.0.0" + strip-indent "^2.0.0" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -26961,6 +27047,11 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -28055,6 +28146,11 @@ trim-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= +trim-newlines@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" + integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= + trim-newlines@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" @@ -28161,6 +28257,19 @@ tsd@^0.13.1: read-pkg-up "^7.0.0" update-notifier "^4.1.0" +tsd@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.7.4.tgz#d9aba567f1394641821a6800dcee60746c87bd03" + integrity sha512-cqr1s2GHtVkU3L/4BXDaeJOjFEuZ7iOVC+hwmyx4G7Eo26mSXCFNnwFm4EasK/MW2HdY3AQWux+AjYzDYLzZow== + dependencies: + eslint-formatter-pretty "^1.3.0" + globby "^9.1.0" + meow "^5.0.0" + path-exists "^3.0.0" + read-pkg-up "^4.0.0" + typescript "^3.0.1" + update-notifier "^2.5.0" + tslib@^1, tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" @@ -28327,7 +28436,7 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@4.0.2, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.7.2: +typescript@4.0.2, typescript@^3.0.1, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.7.2: version "4.0.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== @@ -30575,6 +30684,13 @@ yargs-parser@5.0.0-security.0: camelcase "^3.0.0" object.assign "^4.1.0" +yargs-parser@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" + integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== + dependencies: + camelcase "^4.1.0" + yargs-parser@^18.1.1, yargs-parser@^18.1.2, yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" From 7df0b34f31c3d725fe691f4bc9e458b09a9fb3dd Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Wed, 16 Sep 2020 09:28:58 +0200 Subject: [PATCH 10/15] use `navigateToUrl` to navigate to recent nav links (#77446) --- .../__snapshots__/collapsible_nav.test.tsx.snap | 4 ++++ .../ui/header/__snapshots__/header.test.tsx.snap | 1 + .../chrome/ui/header/collapsible_nav.test.tsx | 1 + .../public/chrome/ui/header/collapsible_nav.tsx | 16 +++++++++++----- src/core/public/chrome/ui/header/header.tsx | 1 + src/core/public/chrome/ui/header/nav_link.tsx | 14 ++++++++++++-- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 86cacfe98f767..1bff6cd9301ed 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -279,6 +279,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` } } navigateToApp={[Function]} + navigateToUrl={[Function]} onIsLockedUpdate={[Function]} recentlyAccessed$={ BehaviorSubject { @@ -2156,6 +2157,7 @@ exports[`CollapsibleNav renders the default nav 1`] = ` } } navigateToApp={[Function]} + navigateToUrl={[Function]} onIsLockedUpdate={[Function]} recentlyAccessed$={ BehaviorSubject { @@ -2391,6 +2393,7 @@ exports[`CollapsibleNav renders the default nav 2`] = ` } } navigateToApp={[Function]} + navigateToUrl={[Function]} onIsLockedUpdate={[Function]} recentlyAccessed$={ BehaviorSubject { @@ -2626,6 +2629,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` } } navigateToApp={[Function]} + navigateToUrl={[Function]} onIsLockedUpdate={[Function]} recentlyAccessed$={ BehaviorSubject { diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 505f242794212..d90d0824a1237 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -5380,6 +5380,7 @@ exports[`Header renders 1`] = ` } } navigateToApp={[MockFunction]} + navigateToUrl={[MockFunction]} onIsLockedUpdate={[Function]} recentlyAccessed$={ BehaviorSubject { diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index e33e76a45580e..267e17dc0a9d0 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -67,6 +67,7 @@ function mockProps() { onIsLockedUpdate: () => {}, closeNav: () => {}, navigateToApp: () => Promise.resolve(), + navigateToUrl: () => Promise.resolve(), customNavLink$: new BehaviorSubject(undefined), }; } diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 01cdb9c38881a..b00e82b660e9f 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -87,6 +87,7 @@ interface Props { onIsLockedUpdate: OnIsLockedUpdate; closeNav: () => void; navigateToApp: InternalApplicationStart['navigateToApp']; + navigateToUrl: InternalApplicationStart['navigateToUrl']; customNavLink$: Rx.Observable; } @@ -100,6 +101,7 @@ export function CollapsibleNav({ onIsLockedUpdate, closeNav, navigateToApp, + navigateToUrl, ...observables }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); @@ -217,17 +219,21 @@ export function CollapsibleNav({ listItems={recentlyAccessed.map((link) => { // TODO #64541 // Can remove icon from recent links completely - const { iconType, ...hydratedLink } = createRecentNavLink(link, navLinks, basePath); + const { iconType, onClick, ...hydratedLink } = createRecentNavLink( + link, + navLinks, + basePath, + navigateToUrl + ); return { ...hydratedLink, 'data-test-subj': 'collapsibleNavAppLink--recent', onClick: (event) => { - if (isModifiedOrPrevented(event)) { - return; + if (!isModifiedOrPrevented(event)) { + closeNav(); + onClick(event); } - - closeNav(); }, }; })} diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 7ec03ea4c6da6..e01a62a54c34d 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -184,6 +184,7 @@ export function Header({ homeHref={homeHref} basePath={basePath} navigateToApp={application.navigateToApp} + navigateToUrl={application.navigateToUrl} onIsLockedUpdate={onIsLockedUpdate} closeNav={() => { setIsNavOpen(false); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 04d9c5bf7a10a..a3c73fc454362 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; import { HttpStart } from '../../../http'; +import { InternalApplicationStart } from '../../../application/types'; import { relativeToAbsolute } from '../../nav_links/to_nav_link'; export const isModifiedOrPrevented = (event: React.MouseEvent) => @@ -87,6 +88,7 @@ export interface RecentNavLink { title: string; 'aria-label': string; iconType?: string; + onClick: React.MouseEventHandler; } /** @@ -102,8 +104,9 @@ export interface RecentNavLink { export function createRecentNavLink( recentLink: ChromeRecentlyAccessedHistoryItem, navLinks: ChromeNavLink[], - basePath: HttpStart['basePath'] -) { + basePath: HttpStart['basePath'], + navigateToUrl: InternalApplicationStart['navigateToUrl'] +): RecentNavLink { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl)); @@ -125,5 +128,12 @@ export function createRecentNavLink( title: titleAndAriaLabel, 'aria-label': titleAndAriaLabel, iconType: navLink?.euiIconType, + /* Use href and onClick to support "open in new tab" and SPA navigation in the same link */ + onClick(event: React.MouseEvent) { + if (event.button === 0 && !isModifiedOrPrevented(event)) { + event.preventDefault(); + navigateToUrl(href); + } + }, }; } From 274d5de2e0df6a674ef0e57298d9d6ed12e1dbac Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 16 Sep 2020 10:47:54 +0300 Subject: [PATCH 11/15] log request body in new ES client (#77150) * log request body in new ES client * add test case when body is null * update test --- .../client/configure_client.test.ts | 219 +++++++++++++++--- .../elasticsearch/client/configure_client.ts | 25 +- 2 files changed, 200 insertions(+), 44 deletions(-) diff --git a/src/core/server/elasticsearch/client/configure_client.test.ts b/src/core/server/elasticsearch/client/configure_client.test.ts index 716e2fd98a5e1..250cfc18a757d 100644 --- a/src/core/server/elasticsearch/client/configure_client.test.ts +++ b/src/core/server/elasticsearch/client/configure_client.test.ts @@ -16,9 +16,11 @@ * specific language governing permissions and limitations * under the License. */ +import { Buffer } from 'buffer'; +import { Readable } from 'stream'; import { RequestEvent, errors } from '@elastic/elasticsearch'; -import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; +import { TransportRequestParams, RequestBody } from '@elastic/elasticsearch/lib/Transport'; import { parseClientOptionsMock, ClientMock } from './configure_client.test.mocks'; import { loggingSystemMock } from '../../logging/logging_system.mock'; @@ -195,40 +197,184 @@ describe('configureClient', () => { `); }); - it('logs each queries if `logQueries` is true', () => { - const client = configureClient( - createFakeConfig({ - logQueries: true, - }), - { logger, scoped: false } - ); + describe('logs each queries if `logQueries` is true', () => { + function createResponseWithBody(body?: RequestBody) { + return createApiResponse({ + body: {}, + statusCode: 200, + params: { + method: 'GET', + path: '/foo', + querystring: { hello: 'dolly' }, + body, + }, + }); + } + + it('when request body is an object', () => { + const client = configureClient( + createFakeConfig({ + logQueries: true, + }), + { logger, scoped: false } + ); + + const response = createResponseWithBody({ + seq_no_primary_term: true, + query: { + term: { user: 'kimchy' }, + }, + }); + + client.emit('response', null, response); + expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(` + Array [ + Array [ + "200 + GET /foo?hello=dolly + {\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}", + Object { + "tags": Array [ + "query", + ], + }, + ], + ] + `); + }); - const response = createApiResponse({ - body: {}, - statusCode: 200, - params: { - method: 'GET', - path: '/foo', - querystring: { hello: 'dolly' }, - }, + it('when request body is a string', () => { + const client = configureClient( + createFakeConfig({ + logQueries: true, + }), + { logger, scoped: false } + ); + + const response = createResponseWithBody( + JSON.stringify({ + seq_no_primary_term: true, + query: { + term: { user: 'kimchy' }, + }, + }) + ); + + client.emit('response', null, response); + expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(` + Array [ + Array [ + "200 + GET /foo?hello=dolly + {\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}", + Object { + "tags": Array [ + "query", + ], + }, + ], + ] + `); }); - client.emit('response', null, response); + it('when request body is a buffer', () => { + const client = configureClient( + createFakeConfig({ + logQueries: true, + }), + { logger, scoped: false } + ); + + const response = createResponseWithBody( + Buffer.from( + JSON.stringify({ + seq_no_primary_term: true, + query: { + term: { user: 'kimchy' }, + }, + }) + ) + ); + + client.emit('response', null, response); + expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(` + Array [ + Array [ + "200 + GET /foo?hello=dolly + [buffer]", + Object { + "tags": Array [ + "query", + ], + }, + ], + ] + `); + }); - expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(` - Array [ + it('when request body is a readable stream', () => { + const client = configureClient( + createFakeConfig({ + logQueries: true, + }), + { logger, scoped: false } + ); + + const response = createResponseWithBody( + // @ts-expect-error definition doesn't know about from + Readable.from( + JSON.stringify({ + seq_no_primary_term: true, + query: { + term: { user: 'kimchy' }, + }, + }) + ) + ); + + client.emit('response', null, response); + expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(` Array [ - "200 - GET /foo - hello=dolly", - Object { - "tags": Array [ - "query", - ], - }, - ], - ] - `); + Array [ + "200 + GET /foo?hello=dolly + [stream]", + Object { + "tags": Array [ + "query", + ], + }, + ], + ] + `); + }); + + it('when request body is not defined', () => { + const client = configureClient( + createFakeConfig({ + logQueries: true, + }), + { logger, scoped: false } + ); + + const response = createResponseWithBody(); + + client.emit('response', null, response); + expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(` + Array [ + Array [ + "200 + GET /foo?hello=dolly", + Object { + "tags": Array [ + "query", + ], + }, + ], + ] + `); + }); }); it('properly encode queries', () => { @@ -255,8 +401,7 @@ describe('configureClient', () => { Array [ Array [ "200 - GET /foo - city=M%C3%BCnich", + GET /foo?city=M%C3%BCnich", Object { "tags": Array [ "query", @@ -286,6 +431,12 @@ describe('configureClient', () => { method: 'GET', path: '/foo', querystring: { hello: 'dolly' }, + body: { + seq_no_primary_term: true, + query: { + term: { user: 'kimchy' }, + }, + }, }, }); client.emit('response', new errors.ResponseError(response), response); @@ -294,8 +445,8 @@ describe('configureClient', () => { Array [ Array [ "500 - GET /foo - hello=dolly", + GET /foo?hello=dolly + {\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}", Object { "tags": Array [ "query", diff --git a/src/core/server/elasticsearch/client/configure_client.ts b/src/core/server/elasticsearch/client/configure_client.ts index a777344813068..bf07ea604d228 100644 --- a/src/core/server/elasticsearch/client/configure_client.ts +++ b/src/core/server/elasticsearch/client/configure_client.ts @@ -16,9 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - +import { Buffer } from 'buffer'; import { stringify } from 'querystring'; import { Client } from '@elastic/elasticsearch'; +import { RequestBody } from '@elastic/elasticsearch/lib/Transport'; + import { Logger } from '../../logging'; import { parseClientOptions, ElasticsearchClientConfig } from './client_config'; @@ -48,15 +50,11 @@ const addLogging = (client: Client, logger: Logger, logQueries: boolean) => { // definition is wrong, `params.querystring` can be either a string or an object const querystring = convertQueryString(params.querystring); - - logger.debug( - `${event.statusCode}\n${params.method} ${params.path}${ - querystring ? `\n${querystring}` : '' - }`, - { - tags: ['query'], - } - ); + const url = `${params.path}${querystring ? `?${querystring}` : ''}`; + const body = params.body ? `\n${ensureString(params.body)}` : ''; + logger.debug(`${event.statusCode}\n${params.method} ${url}${body}`, { + tags: ['query'], + }); } }); }; @@ -67,3 +65,10 @@ const convertQueryString = (qs: string | Record | undefined): strin } return stringify(qs); }; + +function ensureString(body: RequestBody): string { + if (typeof body === 'string') return body; + if (Buffer.isBuffer(body)) return '[buffer]'; + if ('readable' in body && body.readable && typeof body._read === 'function') return '[stream]'; + return JSON.stringify(body); +} From c4eb47a46ba74f5172362ad7d271fd3c809bbe8e Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 16 Sep 2020 11:30:19 +0300 Subject: [PATCH 12/15] [Lens] Settings panel redesign and separate settings per y axis (#76373) * wip, redsign the xy axis general settings * pie chart settings. fix tests, initial implementation * Fix Internationalization * Cleanup * remove unused translations * Add test to check that right axis is enabled * fix test * remove unecessary translation * Added icons and cleaned up some of the visuals for grouped buttons * Fix types * Axis Settings Popover Reusable Component * Legend Popover Reusable Component * Cleanup unused translations * Fix right axis behavior * Revert yLeftTitle to yTitle to avoid migration * PR fixes * identify which axis is enabled * Change the logic on enabling the y axes popovers * Adjust axis popover on horizontal bars * fix failing test and change the logic of fetching the y axis titles * Simpify the axis title logic, make the toolbar repsponsive, add TopAxisIcon * Ui Changes on legends popover * Cleanup and more unit tests * use groupId instead of index to take under consideration all possible scenarios * fix gridlines * Remove ts-ignore from icons and move toolbar button to shared components * Workspace toolbar wraps on smaller devices * Tooltip on Toolbar appears only if the button is disabled * clean up * Add missing translations * fix eslint Co-authored-by: cchaos Co-authored-by: Elastic Machine --- .../lens/public/assets/axis_bottom.tsx | 30 ++ .../plugins/lens/public/assets/axis_left.tsx | 31 ++ .../plugins/lens/public/assets/axis_right.tsx | 31 ++ .../plugins/lens/public/assets/axis_top.tsx | 34 ++ x-pack/plugins/lens/public/assets/legend.tsx | 39 ++ .../_workspace_panel_wrapper.scss | 2 +- .../config_panel/layer_settings.tsx | 2 +- .../workspace_panel/chart_switch.tsx | 2 +- .../workspace_panel_wrapper.tsx | 12 +- .../change_indexpattern.tsx | 2 +- .../lens/public/pie_visualization/toolbar.tsx | 255 ++++------ .../lens/public/shared_components/index.ts | 3 + .../legend_settings_popover.test.tsx | 106 ++++ .../legend_settings_popover.tsx | 158 ++++++ .../shared_components/toolbar_button.scss | 60 +++ .../toolbar_button.tsx | 20 +- .../shared_components/toolbar_popover.tsx | 77 +++ .../lens/public/toolbar_button/index.tsx | 7 - .../public/toolbar_button/toolbar_button.scss | 30 -- .../__snapshots__/to_expression.test.ts.snap | 41 +- .../axes_configuration.test.ts | 18 +- .../xy_visualization/axes_configuration.ts | 32 +- .../axis_settings_popover.test.tsx | 81 +++ .../axis_settings_popover.tsx | 205 ++++++++ .../lens/public/xy_visualization/index.ts | 10 +- .../xy_visualization/to_expression.test.ts | 22 +- .../public/xy_visualization/to_expression.ts | 25 +- .../xy_visualization/tooltip_wrapper.tsx | 31 ++ .../lens/public/xy_visualization/types.ts | 80 ++- .../xy_visualization/xy_config_panel.scss | 4 +- .../xy_visualization/xy_config_panel.test.tsx | 61 +-- .../xy_visualization/xy_config_panel.tsx | 476 +++++++----------- .../xy_visualization/xy_expression.test.tsx | 84 +++- .../public/xy_visualization/xy_expression.tsx | 91 ++-- .../xy_visualization/xy_suggestions.test.ts | 35 +- .../public/xy_visualization/xy_suggestions.ts | 14 +- .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - 38 files changed, 1528 insertions(+), 691 deletions(-) create mode 100644 x-pack/plugins/lens/public/assets/axis_bottom.tsx create mode 100644 x-pack/plugins/lens/public/assets/axis_left.tsx create mode 100644 x-pack/plugins/lens/public/assets/axis_right.tsx create mode 100644 x-pack/plugins/lens/public/assets/axis_top.tsx create mode 100644 x-pack/plugins/lens/public/assets/legend.tsx create mode 100644 x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx create mode 100644 x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx create mode 100644 x-pack/plugins/lens/public/shared_components/toolbar_button.scss rename x-pack/plugins/lens/public/{toolbar_button => shared_components}/toolbar_button.tsx (71%) create mode 100644 x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx delete mode 100644 x-pack/plugins/lens/public/toolbar_button/index.tsx delete mode 100644 x-pack/plugins/lens/public/toolbar_button/toolbar_button.scss create mode 100644 x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx create mode 100644 x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx create mode 100644 x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx diff --git a/x-pack/plugins/lens/public/assets/axis_bottom.tsx b/x-pack/plugins/lens/public/assets/axis_bottom.tsx new file mode 100644 index 0000000000000..9529a93e4c1cc --- /dev/null +++ b/x-pack/plugins/lens/public/assets/axis_bottom.tsx @@ -0,0 +1,30 @@ +/* + * 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 React from 'react'; + +export const EuiIconAxisBottom = ({ + title, + titleId, + ...props +}: { + title: string; + titleId: string; +}) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/lens/public/assets/axis_left.tsx b/x-pack/plugins/lens/public/assets/axis_left.tsx new file mode 100644 index 0000000000000..d1ec0b76a1bd5 --- /dev/null +++ b/x-pack/plugins/lens/public/assets/axis_left.tsx @@ -0,0 +1,31 @@ +/* + * 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 React from 'react'; + +export const EuiIconAxisLeft = ({ + title, + titleId, + ...props +}: { + title: string; + titleId: string; +}) => ( + + {title ? {title} : null} + + + + +); diff --git a/x-pack/plugins/lens/public/assets/axis_right.tsx b/x-pack/plugins/lens/public/assets/axis_right.tsx new file mode 100644 index 0000000000000..e61f87b963a05 --- /dev/null +++ b/x-pack/plugins/lens/public/assets/axis_right.tsx @@ -0,0 +1,31 @@ +/* + * 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 React from 'react'; + +export const EuiIconAxisRight = ({ + title, + titleId, + ...props +}: { + title: string; + titleId: string; +}) => ( + + {title ? {title} : null} + + + + +); diff --git a/x-pack/plugins/lens/public/assets/axis_top.tsx b/x-pack/plugins/lens/public/assets/axis_top.tsx new file mode 100644 index 0000000000000..90fbdc4a21552 --- /dev/null +++ b/x-pack/plugins/lens/public/assets/axis_top.tsx @@ -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 * as React from 'react'; + +export const EuiIconAxisTop = ({ + title, + titleId, + ...props +}: { + title: string; + titleId: string; +}) => ( + + {title ? {title} : null} + + + + + + + +); diff --git a/x-pack/plugins/lens/public/assets/legend.tsx b/x-pack/plugins/lens/public/assets/legend.tsx new file mode 100644 index 0000000000000..d73e68839d9fb --- /dev/null +++ b/x-pack/plugins/lens/public/assets/legend.tsx @@ -0,0 +1,39 @@ +/* + * 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 React from 'react'; + +export const EuiIconLegend = ({ title, titleId, ...props }: { title: string; titleId: string }) => ( + + {title ? {title} : null} + + + + + + + +); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss index 90cc049db96eb..a4d8288d5e600 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss @@ -38,5 +38,5 @@ } .lnsWorkspacePanelWrapper__toolbar { - margin-bottom: $euiSizeS; + margin-bottom: 0; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx index 682316a586626..abbd7e0838bed 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx @@ -9,7 +9,7 @@ import { EuiPopover, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { NativeRenderer } from '../../../native_renderer'; import { Visualization, VisualizationLayerWidgetProps } from '../../../types'; -import { ToolbarButton } from '../../../toolbar_button'; +import { ToolbarButton } from '../../../shared_components'; export function LayerSettings({ layerId, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index 82983862e7c03..f4526cac39c8a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -19,7 +19,7 @@ import { Visualization, FramePublicAPI, Datasource } from '../../../types'; import { Action } from '../state_management'; import { getSuggestions, switchToSuggestion, Suggestion } from '../suggestion_helpers'; import { trackUiEvent } from '../../../lens_ui_telemetry'; -import { ToolbarButton } from '../../../toolbar_button'; +import { ToolbarButton } from '../../../shared_components'; interface VisualizationSelection { visualizationId: string; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx index 901a86bb56e1d..8e7d504ff7677 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx @@ -67,8 +67,14 @@ export function WorkspacePanelWrapper({ ); return ( <> -
- +
+ {activeVisualization && activeVisualization.renderToolbar && ( - + = [ { @@ -99,179 +93,128 @@ const legendOptions: Array<{ id: 'pieLegendDisplay-default', value: 'default', label: i18n.translate('xpack.lens.pieChart.legendVisibility.auto', { - defaultMessage: 'auto', + defaultMessage: 'Auto', }), }, { id: 'pieLegendDisplay-show', value: 'show', label: i18n.translate('xpack.lens.pieChart.legendVisibility.show', { - defaultMessage: 'show', + defaultMessage: 'Show', }), }, { id: 'pieLegendDisplay-hide', value: 'hide', label: i18n.translate('xpack.lens.pieChart.legendVisibility.hide', { - defaultMessage: 'hide', + defaultMessage: 'Hide', }), }, ]; export function PieToolbar(props: VisualizationToolbarProps) { - const [open, setOpen] = useState(false); const { state, setState } = props; const layer = state.layers[0]; if (!layer) { return null; } return ( - - - { - setOpen(!open); - }} - > - {i18n.translate('xpack.lens.pieChart.settingsLabel', { defaultMessage: 'Settings' })} - - } - isOpen={open} - closePopover={() => { - setOpen(false); - }} - anchorPosition="downRight" + + + - - { - setState({ - ...state, - layers: [{ ...layer, categoryDisplay: option }], - }); - }} - /> - - - { - setState({ - ...state, - layers: [{ ...layer, numberDisplay: option }], - }); - }} - /> - - - - - setState({ - ...state, - layers: [{ ...layer, percentDecimals: value }], - }) - } - /> - - - -
- value === layer.legendDisplay)!.id} - onChange={(optionId) => { - setState({ - ...state, - layers: [ - { - ...layer, - legendDisplay: legendOptions.find(({ id }) => id === optionId)!.value, - }, - ], - }); - }} - buttonSize="compressed" - isFullWidth - /> - - - { - setState({ ...state, layers: [{ ...layer, nestedLegend: !layer.nestedLegend }] }); - }} - /> -
-
- - { - setState({ - ...state, - layers: [{ ...layer, legendPosition: e.target.value as Position }], - }); - }} - /> - -
-
+ { + setState({ + ...state, + layers: [{ ...layer, categoryDisplay: option }], + }); + }} + /> + + + { + setState({ + ...state, + layers: [{ ...layer, numberDisplay: option }], + }); + }} + /> + + + + + setState({ + ...state, + layers: [{ ...layer, percentDecimals: value }], + }) + } + /> + + + { + setState({ + ...state, + layers: [ + { + ...layer, + legendDisplay: legendOptions.find(({ id }) => id === optionId)!.value, + }, + ], + }); + }} + position={layer.legendPosition} + onPositionChange={(id) => { + setState({ + ...state, + layers: [{ ...layer, legendPosition: id as Position }], + }); + }} + renderNestedLegendSwitch + nestedLegend={!!layer.nestedLegend} + onNestedLegendChange={() => { + setState({ + ...state, + layers: [{ ...layer, nestedLegend: !layer.nestedLegend }], + }); + }} + />
); } diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index ad662fd7a59d9..c0362a5660adb 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -5,3 +5,6 @@ */ export * from './empty_placeholder'; +export { ToolbarPopoverProps, ToolbarPopover } from './toolbar_popover'; +export { ToolbarButtonProps, ToolbarButton } from './toolbar_button'; +export { LegendSettingsPopover } from './legend_settings_popover'; diff --git a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx new file mode 100644 index 0000000000000..1e0e6b33b6cd4 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx @@ -0,0 +1,106 @@ +/* + * 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 { Position } from '@elastic/charts'; +import { shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; +import { LegendSettingsPopover, LegendSettingsPopoverProps } from './legend_settings_popover'; + +describe('Legend Settings', () => { + const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [ + { + id: `test_legend_auto`, + value: 'auto', + label: 'Auto', + }, + { + id: `test_legend_show`, + value: 'show', + label: 'Show', + }, + { + id: `test_legend_hide`, + value: 'hide', + label: 'Hide', + }, + ]; + let props: LegendSettingsPopoverProps; + beforeEach(() => { + props = { + legendOptions, + mode: 'auto', + onDisplayChange: jest.fn(), + onPositionChange: jest.fn(), + }; + }); + + it('should have selected the given mode as Display value', () => { + const component = shallow(); + expect(component.find('[data-test-subj="lens-legend-display-btn"]').prop('idSelected')).toEqual( + 'test_legend_auto' + ); + }); + + it('should have called the onDisplayChange function on ButtonGroup change', () => { + const component = shallow(); + component.find('[data-test-subj="lens-legend-display-btn"]').simulate('change'); + expect(props.onDisplayChange).toHaveBeenCalled(); + }); + + it('should have default the Position to right when no position is given', () => { + const component = shallow(); + expect( + component.find('[data-test-subj="lens-legend-position-btn"]').prop('idSelected') + ).toEqual(Position.Right); + }); + + it('should have called the onPositionChange function on ButtonGroup change', () => { + const component = shallow(); + component.find('[data-test-subj="lens-legend-position-btn"]').simulate('change'); + expect(props.onPositionChange).toHaveBeenCalled(); + }); + + it('should disable the position button group on hide mode', () => { + const component = shallow(); + expect( + component.find('[data-test-subj="lens-legend-position-btn"]').prop('isDisabled') + ).toEqual(true); + }); + + it('should enable the Nested Legend Switch when renderNestedLegendSwitch prop is true', () => { + const component = shallow(); + expect(component.find('[data-test-subj="lens-legend-nested-switch"]')).toHaveLength(1); + }); + + it('should set the switch state on nestedLegend prop value', () => { + const component = shallow( + + ); + expect(component.find('[data-test-subj="lens-legend-nested-switch"]').prop('checked')).toEqual( + true + ); + }); + + it('should have called the onNestedLegendChange function on switch change', () => { + const nestedProps = { + ...props, + renderNestedLegendSwitch: true, + onNestedLegendChange: jest.fn(), + }; + const component = shallow(); + component.find('[data-test-subj="lens-legend-nested-switch"]').simulate('change'); + expect(nestedProps.onNestedLegendChange).toHaveBeenCalled(); + }); + + it('should disable switch group on hide mode', () => { + const component = shallow( + + ); + expect(component.find('[data-test-subj="lens-legend-nested-switch"]').prop('disabled')).toEqual( + true + ); + }); +}); diff --git a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx new file mode 100644 index 0000000000000..b3df4814b85f8 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx @@ -0,0 +1,158 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiButtonGroup, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import { Position } from '@elastic/charts'; +import { ToolbarPopover } from '../shared_components'; + +export interface LegendSettingsPopoverProps { + /** + * Determines the legend display options + */ + legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide' | 'default'; label: string }>; + /** + * Determines the legend mode + */ + mode: 'default' | 'show' | 'hide' | 'auto'; + /** + * Callback on display option change + */ + onDisplayChange: (id: string) => void; + /** + * Sets the legend position + */ + position?: Position; + /** + * Callback on position option change + */ + onPositionChange: (id: string) => void; + /** + * If true, nested legend switch is rendered + */ + renderNestedLegendSwitch?: boolean; + /** + * nested legend switch status + */ + nestedLegend?: boolean; + /** + * Callback on nested switch status change + */ + onNestedLegendChange?: (event: EuiSwitchEvent) => void; +} + +const toggleButtonsIcons = [ + { + id: Position.Bottom, + label: i18n.translate('xpack.lens.shared.legendPositionBottom', { + defaultMessage: 'Bottom', + }), + iconType: 'arrowDown', + }, + { + id: Position.Left, + label: i18n.translate('xpack.lens.shared.legendPositionLeft', { + defaultMessage: 'Left', + }), + iconType: 'arrowLeft', + }, + { + id: Position.Right, + label: i18n.translate('xpack.lens.shared.legendPositionRight', { + defaultMessage: 'Right', + }), + iconType: 'arrowRight', + }, + { + id: Position.Top, + label: i18n.translate('xpack.lens.shared.legendPositionTop', { + defaultMessage: 'Top', + }), + iconType: 'arrowUp', + }, +]; + +export const LegendSettingsPopover: React.FunctionComponent = ({ + legendOptions, + mode, + onDisplayChange, + position, + onPositionChange, + renderNestedLegendSwitch, + nestedLegend, + onNestedLegendChange = () => {}, +}) => { + return ( + + + value === mode)!.id} + onChange={onDisplayChange} + /> + + + + + {renderNestedLegendSwitch && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/lens/public/shared_components/toolbar_button.scss b/x-pack/plugins/lens/public/shared_components/toolbar_button.scss new file mode 100644 index 0000000000000..61b02f47678c3 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/toolbar_button.scss @@ -0,0 +1,60 @@ +.lnsToolbarButton { + line-height: $euiButtonHeight; // Keeps alignment of text and chart icon + background-color: $euiColorEmptyShade; + + // Some toolbar buttons are just icons, but EuiButton comes with margin and min-width that need to be removed + min-width: 0; + + &[class*='--text'] { + // Lighten the border color for all states + border-color: $euiBorderColor !important; // sass-lint:disable-line no-important + } + + &[class*='isDisabled'] { + // There is a popover `pointer-events: none` that messes with the not-allowed cursor + pointer-events: initial; + } + + .lnsToolbarButton__text > svg { + margin-top: -1px; // Just some weird alignment issue when icon is the child not the `iconType` + } + + .lnsToolbarButton__text:empty { + margin: 0; + } + + // Toolbar buttons don't look good with centered text when fullWidth + &[class*='fullWidth'] { + text-align: left; + + .lnsToolbarButton__content { + justify-content: space-between; + } + } + +} + +.lnsToolbarButton--groupLeft { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.lnsToolbarButton--groupCenter { + border-radius: 0; + border-left: none; +} + +.lnsToolbarButton--groupRight { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: none; +} + +.lnsToolbarButton--bold { + font-weight: $euiFontWeightBold; +} + +.lnsToolbarButton--s { + box-shadow: none !important; // sass-lint:disable-line no-important + font-size: $euiFontSizeS; +} diff --git a/x-pack/plugins/lens/public/toolbar_button/toolbar_button.tsx b/x-pack/plugins/lens/public/shared_components/toolbar_button.tsx similarity index 71% rename from x-pack/plugins/lens/public/toolbar_button/toolbar_button.tsx rename to x-pack/plugins/lens/public/shared_components/toolbar_button.tsx index 0a63781818171..56647352750a1 100644 --- a/x-pack/plugins/lens/public/toolbar_button/toolbar_button.tsx +++ b/x-pack/plugins/lens/public/shared_components/toolbar_button.tsx @@ -9,6 +9,13 @@ import React from 'react'; import classNames from 'classnames'; import { EuiButton, PropsOf, EuiButtonProps } from '@elastic/eui'; +const groupPositionToClassMap = { + none: null, + left: 'lnsToolbarButton--groupLeft', + center: 'lnsToolbarButton--groupCenter', + right: 'lnsToolbarButton--groupRight', +}; + export type ToolbarButtonProps = PropsOf & { /** * Determines prominence @@ -18,6 +25,14 @@ export type ToolbarButtonProps = PropsOf & { * Smaller buttons also remove extra shadow for less prominence */ size?: EuiButtonProps['size']; + /** + * Determines if the button will have a down arrow or not + */ + hasArrow?: boolean; + /** + * Adjusts the borders for groupings + */ + groupPosition?: 'none' | 'left' | 'center' | 'right'; }; export const ToolbarButton: React.FunctionComponent = ({ @@ -25,10 +40,13 @@ export const ToolbarButton: React.FunctionComponent = ({ className, fontWeight = 'normal', size = 'm', + hasArrow = true, + groupPosition = 'none', ...rest }) => { const classes = classNames( 'lnsToolbarButton', + groupPositionToClassMap[groupPosition], [`lnsToolbarButton--${fontWeight}`, `lnsToolbarButton--${size}`], className ); @@ -36,7 +54,7 @@ export const ToolbarButton: React.FunctionComponent = ({ = ({ + children, + title, + type, + isDisabled = false, + groupPosition, +}) => { + const [open, setOpen] = useState(false); + + const iconType: string | IconType = typeof type === 'string' ? typeToIconMap[type] : type; + + return ( + + { + setOpen(!open); + }} + hasArrow={false} + isDisabled={isDisabled} + groupPosition={groupPosition} + > + + + } + isOpen={open} + closePopover={() => { + setOpen(false); + }} + anchorPosition="downRight" + > + {title} + {children} + + + ); +}; diff --git a/x-pack/plugins/lens/public/toolbar_button/index.tsx b/x-pack/plugins/lens/public/toolbar_button/index.tsx deleted file mode 100644 index ee6489726a0a7..0000000000000 --- a/x-pack/plugins/lens/public/toolbar_button/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { ToolbarButtonProps, ToolbarButton } from './toolbar_button'; diff --git a/x-pack/plugins/lens/public/toolbar_button/toolbar_button.scss b/x-pack/plugins/lens/public/toolbar_button/toolbar_button.scss deleted file mode 100644 index f36fdfdf02aba..0000000000000 --- a/x-pack/plugins/lens/public/toolbar_button/toolbar_button.scss +++ /dev/null @@ -1,30 +0,0 @@ -.lnsToolbarButton { - line-height: $euiButtonHeight; // Keeps alignment of text and chart icon - background-color: $euiColorEmptyShade; - border-color: $euiBorderColor; - - // Some toolbar buttons are just icons, but EuiButton comes with margin and min-width that need to be removed - min-width: 0; - - .lnsToolbarButton__text:empty { - margin: 0; - } - - // Toolbar buttons don't look good with centered text when fullWidth - &[class*='fullWidth'] { - text-align: left; - - .lnsToolbarButton__content { - justify-content: space-between; - } - } -} - -.lnsToolbarButton--bold { - font-weight: $euiFontWeightBold; -} - -.lnsToolbarButton--s { - box-shadow: none !important; // sass-lint:disable-line no-important - font-size: $euiFontSizeS; -} diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap index 19ea75239ddb2..dd8c6377cacdc 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap @@ -5,6 +5,28 @@ Object { "chain": Array [ Object { "arguments": Object { + "axisTitlesVisibilitySettings": Array [ + Object { + "chain": Array [ + Object { + "arguments": Object { + "x": Array [ + true, + ], + "yLeft": Array [ + true, + ], + "yRight": Array [ + true, + ], + }, + "function": "lens_xy_axisTitlesVisibilityConfig", + "type": "function", + }, + ], + "type": "expression", + }, + ], "fittingFunction": Array [ "Carry", ], @@ -16,7 +38,10 @@ Object { "x": Array [ false, ], - "y": Array [ + "yLeft": Array [ + true, + ], + "yRight": Array [ true, ], }, @@ -92,12 +117,6 @@ Object { "type": "expression", }, ], - "showXAxisTitle": Array [ - true, - ], - "showYAxisTitle": Array [ - true, - ], "tickLabelsVisibilitySettings": Array [ Object { "chain": Array [ @@ -106,7 +125,10 @@ Object { "x": Array [ false, ], - "y": Array [ + "yLeft": Array [ + true, + ], + "yRight": Array [ true, ], }, @@ -120,6 +142,9 @@ Object { "xTitle": Array [ "", ], + "yRightTitle": Array [ + "", + ], "yTitle": Array [ "", ], diff --git a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts index 7b0edf2b367be..15c08d17e49c6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts @@ -203,7 +203,7 @@ describe('axes_configuration', () => { it('should map auto series to left axis', () => { const formatFactory = jest.fn(); - const groups = getAxesConfiguration([sampleLayer], tables, formatFactory, false); + const groups = getAxesConfiguration([sampleLayer], false, tables, formatFactory); expect(groups.length).toEqual(1); expect(groups[0].position).toEqual('left'); expect(groups[0].series[0].accessor).toEqual('yAccessorId'); @@ -213,7 +213,7 @@ describe('axes_configuration', () => { it('should map auto series to right axis if formatters do not match', () => { const formatFactory = jest.fn(); const twoSeriesLayer = { ...sampleLayer, accessors: ['yAccessorId', 'yAccessorId2'] }; - const groups = getAxesConfiguration([twoSeriesLayer], tables, formatFactory, false); + const groups = getAxesConfiguration([twoSeriesLayer], false, tables, formatFactory); expect(groups.length).toEqual(2); expect(groups[0].position).toEqual('left'); expect(groups[1].position).toEqual('right'); @@ -227,7 +227,7 @@ describe('axes_configuration', () => { ...sampleLayer, accessors: ['yAccessorId', 'yAccessorId2', 'yAccessorId3'], }; - const groups = getAxesConfiguration([threeSeriesLayer], tables, formatFactory, false); + const groups = getAxesConfiguration([threeSeriesLayer], false, tables, formatFactory); expect(groups.length).toEqual(2); expect(groups[0].position).toEqual('left'); expect(groups[1].position).toEqual('right'); @@ -240,9 +240,9 @@ describe('axes_configuration', () => { const formatFactory = jest.fn(); const groups = getAxesConfiguration( [{ ...sampleLayer, yConfig: [{ forAccessor: 'yAccessorId', axisMode: 'right' }] }], + false, tables, - formatFactory, - false + formatFactory ); expect(groups.length).toEqual(1); expect(groups[0].position).toEqual('right'); @@ -260,9 +260,9 @@ describe('axes_configuration', () => { yConfig: [{ forAccessor: 'yAccessorId', axisMode: 'right' }], }, ], + false, tables, - formatFactory, - false + formatFactory ); expect(groups.length).toEqual(2); expect(groups[0].position).toEqual('left'); @@ -284,9 +284,9 @@ describe('axes_configuration', () => { yConfig: [{ forAccessor: 'yAccessorId', axisMode: 'right' }], }, ], + false, tables, - formatFactory, - false + formatFactory ); expect(formatFactory).toHaveBeenCalledTimes(2); expect(formatFactory).toHaveBeenCalledWith({ id: 'number' }); diff --git a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts index 006995d92a926..876baaabb57c5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts +++ b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts @@ -20,7 +20,7 @@ interface FormattedMetric { type GroupsConfiguration = Array<{ groupId: string; position: 'left' | 'right' | 'bottom' | 'top'; - formatter: IFieldFormat; + formatter?: IFieldFormat; series: Array<{ layer: string; accessor: string }>; }>; @@ -33,9 +33,9 @@ export function isFormatterCompatible( export function getAxesConfiguration( layers: LayerConfig[], - tables: Record, - formatFactory: (mapping: SerializedFieldFormat) => IFieldFormat, - shouldRotate: boolean + shouldRotate: boolean, + tables?: Record, + formatFactory?: (mapping: SerializedFieldFormat) => IFieldFormat ): GroupsConfiguration { const series: { auto: FormattedMetric[]; left: FormattedMetric[]; right: FormattedMetric[] } = { auto: [], @@ -43,13 +43,13 @@ export function getAxesConfiguration( right: [], }; - layers.forEach((layer) => { - const table = tables[layer.layerId]; + layers?.forEach((layer) => { + const table = tables?.[layer.layerId]; layer.accessors.forEach((accessor) => { const mode = layer.yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode || 'auto'; - let formatter: SerializedFieldFormat = table.columns.find((column) => column.id === accessor) + let formatter: SerializedFieldFormat = table?.columns.find((column) => column.id === accessor) ?.formatHint || { id: 'number' }; if (layer.seriesType.includes('percentage') && formatter.id !== 'percent') { formatter = { @@ -70,16 +70,18 @@ export function getAxesConfiguration( series.auto.forEach((currentSeries) => { if ( series.left.length === 0 || - series.left.every((leftSeries) => - isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat) - ) + (tables && + series.left.every((leftSeries) => + isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat) + )) ) { series.left.push(currentSeries); } else if ( series.right.length === 0 || - series.right.every((rightSeries) => - isFormatterCompatible(rightSeries.fieldFormat, currentSeries.fieldFormat) - ) + (tables && + series.left.every((leftSeries) => + isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat) + )) ) { series.right.push(currentSeries); } else if (series.right.length >= series.left.length) { @@ -95,7 +97,7 @@ export function getAxesConfiguration( axisGroups.push({ groupId: 'left', position: shouldRotate ? 'bottom' : 'left', - formatter: formatFactory(series.left[0].fieldFormat), + formatter: formatFactory?.(series.left[0].fieldFormat), series: series.left.map(({ fieldFormat, ...currentSeries }) => currentSeries), }); } @@ -104,7 +106,7 @@ export function getAxesConfiguration( axisGroups.push({ groupId: 'right', position: shouldRotate ? 'top' : 'right', - formatter: formatFactory(series.right[0].fieldFormat), + formatter: formatFactory?.(series.right[0].fieldFormat), series: series.right.map(({ fieldFormat, ...currentSeries }) => currentSeries), }); } diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx new file mode 100644 index 0000000000000..9e71323377c1a --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx @@ -0,0 +1,81 @@ +/* + * 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 { shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; +import { AxisSettingsPopover, AxisSettingsPopoverProps } from './axis_settings_popover'; +import { ToolbarPopover } from '../shared_components'; + +describe('Axes Settings', () => { + let props: AxisSettingsPopoverProps; + beforeEach(() => { + props = { + layers: [ + { + seriesType: 'bar', + layerId: 'first', + splitAccessor: 'baz', + xAccessor: 'foo', + accessors: ['bar'], + }, + ], + updateTitleState: jest.fn(), + axisTitle: 'My custom X axis title', + axis: 'x', + areTickLabelsVisible: true, + areGridlinesVisible: true, + isAxisTitleVisible: true, + toggleAxisTitleVisibility: jest.fn(), + toggleTickLabelsVisibility: jest.fn(), + toggleGridlinesVisibility: jest.fn(), + }; + }); + + it('should disable the popover if the isDisabled property is true', () => { + const component = shallow(); + expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true); + }); + + it('should show the axes title on the corresponding input text', () => { + const component = shallow(); + expect(component.find('[data-test-subj="lnsxAxisTitle"]').prop('value')).toBe( + 'My custom X axis title' + ); + }); + + it('should disable the input text if the switch is off', () => { + const component = shallow(); + expect(component.find('[data-test-subj="lnsxAxisTitle"]').prop('disabled')).toBe(true); + }); + + it('has the tickLabels switch on by default', () => { + const component = shallow(); + expect(component.find('[data-test-subj="lnsshowxAxisTickLabels"]').prop('checked')).toBe(true); + }); + + it('has the tickLabels switch off when tickLabelsVisibilitySettings for this axes are false', () => { + const component = shallow( + + ); + expect(component.find('[data-test-subj="lnsshowyLeftAxisTickLabels"]').prop('checked')).toBe( + false + ); + }); + + it('has the gridlines switch on by default', () => { + const component = shallow(); + expect(component.find('[data-test-subj="lnsshowxAxisGridlines"]').prop('checked')).toBe(true); + }); + + it('has the gridlines switch off when gridlinesVisibilitySettings for this axes are false', () => { + const component = shallow( + + ); + expect(component.find('[data-test-subj="lnsshowyRightAxisGridlines"]').prop('checked')).toBe( + false + ); + }); +}); diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx new file mode 100644 index 0000000000000..835f3e2cde769 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSwitch, + EuiSpacer, + EuiFieldText, + IconType, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { LayerConfig, AxesSettingsConfig } from './types'; +import { ToolbarPopover, ToolbarButtonProps } from '../shared_components'; +import { isHorizontalChart } from './state_helpers'; +import { EuiIconAxisBottom } from '../assets/axis_bottom'; +import { EuiIconAxisLeft } from '../assets/axis_left'; +import { EuiIconAxisRight } from '../assets/axis_right'; +import { EuiIconAxisTop } from '../assets/axis_top'; + +type AxesSettingsConfigKeys = keyof AxesSettingsConfig; +export interface AxisSettingsPopoverProps { + /** + * Determines the axis + */ + axis: AxesSettingsConfigKeys; + /** + * Contains the chart layers + */ + layers?: LayerConfig[]; + /** + * Determines the axis title + */ + axisTitle: string | undefined; + /** + * Callback to axis title change + */ + updateTitleState: (value: string) => void; + /** + * Determines if the popover is Disabled + */ + isDisabled?: boolean; + /** + * Determines if the ticklabels of the axis are visible + */ + areTickLabelsVisible: boolean; + /** + * Toggles the axis tickLabels visibility + */ + toggleTickLabelsVisibility: (axis: AxesSettingsConfigKeys) => void; + /** + * Determines if the gridlines of the axis are visible + */ + areGridlinesVisible: boolean; + /** + * Toggles the gridlines visibility + */ + toggleGridlinesVisibility: (axis: AxesSettingsConfigKeys) => void; + /** + * Determines if the title visibility switch is on and the input text is disabled + */ + isAxisTitleVisible: boolean; + /** + * Toggles the axis title visibility + */ + toggleAxisTitleVisibility: (axis: AxesSettingsConfigKeys, checked: boolean) => void; +} +const popoverConfig = ( + axis: AxesSettingsConfigKeys, + isHorizontal: boolean +): { icon: IconType; groupPosition: ToolbarButtonProps['groupPosition']; popoverTitle: string } => { + switch (axis) { + case 'yLeft': + return { + icon: (isHorizontal ? EuiIconAxisBottom : EuiIconAxisLeft) as IconType, + groupPosition: 'left', + popoverTitle: isHorizontal + ? i18n.translate('xpack.lens.xyChart.bottomAxisLabel', { + defaultMessage: 'Bottom axis', + }) + : i18n.translate('xpack.lens.xyChart.leftAxisLabel', { + defaultMessage: 'Left axis', + }), + }; + case 'yRight': + return { + icon: (isHorizontal ? EuiIconAxisTop : EuiIconAxisRight) as IconType, + groupPosition: 'right', + popoverTitle: isHorizontal + ? i18n.translate('xpack.lens.xyChart.topAxisLabel', { + defaultMessage: 'Top axis', + }) + : i18n.translate('xpack.lens.xyChart.rightAxisLabel', { + defaultMessage: 'Right axis', + }), + }; + case 'x': + default: + return { + icon: (isHorizontal ? EuiIconAxisLeft : EuiIconAxisBottom) as IconType, + groupPosition: 'center', + popoverTitle: isHorizontal + ? i18n.translate('xpack.lens.xyChart.leftAxisLabel', { + defaultMessage: 'Left axis', + }) + : i18n.translate('xpack.lens.xyChart.bottomAxisLabel', { + defaultMessage: 'Bottom axis', + }), + }; + } +}; + +export const AxisSettingsPopover: React.FunctionComponent = ({ + layers, + axis, + axisTitle, + updateTitleState, + toggleTickLabelsVisibility, + toggleGridlinesVisibility, + isDisabled, + areTickLabelsVisible, + areGridlinesVisible, + isAxisTitleVisible, + toggleAxisTitleVisibility, +}) => { + const [title, setTitle] = useState(axisTitle); + + const isHorizontal = layers?.length ? isHorizontalChart(layers) : false; + const config = popoverConfig(axis, isHorizontal); + + const onTitleChange = (value: string): void => { + setTitle(value); + updateTitleState(value); + }; + return ( + + + + +

+ {i18n.translate('xpack.lens.xyChart.axisNameLabel', { + defaultMessage: 'Axis name', + })} +

+
+
+ + toggleAxisTitleVisibility(axis, target.checked)} + checked={isAxisTitleVisible} + /> + +
+ + onTitleChange(target.value)} + aria-label={i18n.translate('xpack.lens.xyChart.overwriteAxisTitle', { + defaultMessage: 'Overwrite axis title', + })} + /> + + toggleTickLabelsVisibility(axis)} + checked={areTickLabelsVisible} + /> + + toggleGridlinesVisibility(axis)} + checked={areGridlinesVisible} + /> +
+ ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts index fddcad7989b25..470d197e847eb 100644 --- a/x-pack/plugins/lens/public/xy_visualization/index.ts +++ b/x-pack/plugins/lens/public/xy_visualization/index.ts @@ -10,7 +10,14 @@ import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public' import { UI_SETTINGS } from '../../../../../src/plugins/data/public'; import { xyVisualization } from './xy_visualization'; import { xyChart, getXyChartRenderer } from './xy_expression'; -import { legendConfig, layerConfig, yAxisConfig, tickLabelsConfig, gridlinesConfig } from './types'; +import { + legendConfig, + layerConfig, + yAxisConfig, + tickLabelsConfig, + gridlinesConfig, + axisTitlesVisibilityConfig, +} from './types'; import { EditorFrameSetup, FormatFactory } from '../types'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; @@ -41,6 +48,7 @@ export class XyVisualization { expressions.registerFunction(() => yAxisConfig); expressions.registerFunction(() => tickLabelsConfig); expressions.registerFunction(() => gridlinesConfig); + expressions.registerFunction(() => axisTitlesVisibilityConfig); expressions.registerFunction(() => layerConfig); expressions.registerFunction(() => xyChart); diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts index 825281d6d88c2..d09ba01b32c66 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts @@ -41,8 +41,8 @@ describe('#toExpression', () => { legend: { position: Position.Bottom, isVisible: true }, preferredSeriesType: 'bar', fittingFunction: 'Carry', - tickLabelsVisibilitySettings: { x: false, y: true }, - gridlinesVisibilitySettings: { x: false, y: true }, + tickLabelsVisibilitySettings: { x: false, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: false, yLeft: true, yRight: true }, layers: [ { layerId: 'first', @@ -79,7 +79,7 @@ describe('#toExpression', () => { ).toEqual('None'); }); - it('should default the showXAxisTitle and showYAxisTitle to true', () => { + it('should default the axisTitles visibility settings to true', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, @@ -96,8 +96,13 @@ describe('#toExpression', () => { }, frame.datasourceLayers ) as Ast; - expect(expression.chain[0].arguments.showXAxisTitle[0]).toBe(true); - expect(expression.chain[0].arguments.showYAxisTitle[0]).toBe(true); + expect( + (expression.chain[0].arguments.axisTitlesVisibilitySettings[0] as Ast).chain[0].arguments + ).toEqual({ + x: [true], + yLeft: [true], + yRight: [true], + }); }); it('should generate an expression without x accessor', () => { @@ -166,6 +171,7 @@ describe('#toExpression', () => { expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('d'); expect(expression.chain[0].arguments.xTitle).toEqual(['']); expect(expression.chain[0].arguments.yTitle).toEqual(['']); + expect(expression.chain[0].arguments.yRightTitle).toEqual(['']); expect( (expression.chain[0].arguments.layers[0] as Ast).chain[0].arguments.columnToLabel ).toEqual([ @@ -198,7 +204,8 @@ describe('#toExpression', () => { (expression.chain[0].arguments.tickLabelsVisibilitySettings[0] as Ast).chain[0].arguments ).toEqual({ x: [true], - y: [true], + yLeft: [true], + yRight: [true], }); }); @@ -223,7 +230,8 @@ describe('#toExpression', () => { (expression.chain[0].arguments.gridlinesVisibilitySettings[0] as Ast).chain[0].arguments ).toEqual({ x: [true], - y: [true], + yLeft: [true], + yRight: [true], }); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index f64624776186d..df8d571a1fdf8 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -99,6 +99,7 @@ export const buildExpression = ( arguments: { xTitle: [state.xTitle || ''], yTitle: [state.yTitle || ''], + yRightTitle: [state.yRightTitle || ''], legend: [ { type: 'expression', @@ -118,8 +119,22 @@ export const buildExpression = ( }, ], fittingFunction: [state.fittingFunction || 'None'], - showXAxisTitle: [state.showXAxisTitle ?? true], - showYAxisTitle: [state.showYAxisTitle ?? true], + axisTitlesVisibilitySettings: [ + { + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_xy_axisTitlesVisibilityConfig', + arguments: { + x: [state?.axisTitlesVisibilitySettings?.x ?? true], + yLeft: [state?.axisTitlesVisibilitySettings?.yLeft ?? true], + yRight: [state?.axisTitlesVisibilitySettings?.yRight ?? true], + }, + }, + ], + }, + ], tickLabelsVisibilitySettings: [ { type: 'expression', @@ -129,7 +144,8 @@ export const buildExpression = ( function: 'lens_xy_tickLabelsConfig', arguments: { x: [state?.tickLabelsVisibilitySettings?.x ?? true], - y: [state?.tickLabelsVisibilitySettings?.y ?? true], + yLeft: [state?.tickLabelsVisibilitySettings?.yLeft ?? true], + yRight: [state?.tickLabelsVisibilitySettings?.yRight ?? true], }, }, ], @@ -144,7 +160,8 @@ export const buildExpression = ( function: 'lens_xy_gridlinesConfig', arguments: { x: [state?.gridlinesVisibilitySettings?.x ?? true], - y: [state?.gridlinesVisibilitySettings?.y ?? true], + yLeft: [state?.gridlinesVisibilitySettings?.yLeft ?? true], + yRight: [state?.gridlinesVisibilitySettings?.yRight ?? true], }, }, ], diff --git a/x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx b/x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx new file mode 100644 index 0000000000000..cdbec3fd5d6ae --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx @@ -0,0 +1,31 @@ +/* + * 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 { EuiToolTip } from '@elastic/eui'; + +export interface TooltipWrapperProps { + tooltipContent: string; + condition: boolean; +} + +export const TooltipWrapper: React.FunctionComponent = ({ + children, + condition, + tooltipContent, +}) => { + return ( + <> + {condition ? ( + + <>{children} + + ) : ( + children + )} + + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index 8438b1f27dd0d..185fa20f169ee 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -80,7 +80,8 @@ export const legendConfig: ExpressionFunctionDefinition< export interface AxesSettingsConfig { x: boolean; - y: boolean; + yLeft: boolean; + yRight: boolean; } type TickLabelsConfigResult = AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' }; @@ -103,10 +104,16 @@ export const tickLabelsConfig: ExpressionFunctionDefinition< defaultMessage: 'Specifies whether or not the tick labels of the x-axis are visible.', }), }, - y: { + yLeft: { types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.yAxisTickLabels.help', { - defaultMessage: 'Specifies whether or not the tick labels of the y-axis are visible.', + help: i18n.translate('xpack.lens.xyChart.yLeftAxisTickLabels.help', { + defaultMessage: 'Specifies whether or not the tick labels of the left y-axis are visible.', + }), + }, + yRight: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yRightAxisTickLabels.help', { + defaultMessage: 'Specifies whether or not the tick labels of the right y-axis are visible.', }), }, }, @@ -138,10 +145,16 @@ export const gridlinesConfig: ExpressionFunctionDefinition< defaultMessage: 'Specifies whether or not the gridlines of the x-axis are visible.', }), }, - y: { + yLeft: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yLeftAxisgridlines.help', { + defaultMessage: 'Specifies whether or not the gridlines of the left y-axis are visible.', + }), + }, + yRight: { types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.yAxisgridlines.help', { - defaultMessage: 'Specifies whether or not the gridlines of the y-axis are visible.', + help: i18n.translate('xpack.lens.xyChart.yRightAxisgridlines.help', { + defaultMessage: 'Specifies whether or not the gridlines of the right y-axis are visible.', }), }, }, @@ -153,6 +166,49 @@ export const gridlinesConfig: ExpressionFunctionDefinition< }, }; +type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & { + type: 'lens_xy_axisTitlesVisibilityConfig'; +}; + +export const axisTitlesVisibilityConfig: ExpressionFunctionDefinition< + 'lens_xy_axisTitlesVisibilityConfig', + null, + AxesSettingsConfig, + AxisTitlesVisibilityConfigResult +> = { + name: 'lens_xy_axisTitlesVisibilityConfig', + aliases: [], + type: 'lens_xy_axisTitlesVisibilityConfig', + help: `Configure the xy chart's axis titles appearance`, + inputTypes: ['null'], + args: { + x: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.xAxisTitle.help', { + defaultMessage: 'Specifies whether or not the title of the x-axis are visible.', + }), + }, + yLeft: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yLeftAxisTitle.help', { + defaultMessage: 'Specifies whether or not the title of the left y-axis are visible.', + }), + }, + yRight: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yRightAxisTitle.help', { + defaultMessage: 'Specifies whether or not the title of the right y-axis are visible.', + }), + }, + }, + fn: function fn(input: unknown, args: AxesSettingsConfig) { + return { + type: 'lens_xy_axisTitlesVisibilityConfig', + ...args, + }; + }, +}; + interface AxisConfig { title: string; hide?: boolean; @@ -329,11 +385,13 @@ export type LayerArgs = LayerConfig & { export interface XYArgs { xTitle: string; yTitle: string; + yRightTitle: string; legend: LegendConfig & { type: 'lens_xy_legendConfig' }; layers: LayerArgs[]; fittingFunction?: FittingFunction; - showXAxisTitle?: boolean; - showYAxisTitle?: boolean; + axisTitlesVisibilitySettings?: AxesSettingsConfig & { + type: 'lens_xy_axisTitlesVisibilityConfig'; + }; tickLabelsVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' }; gridlinesVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' }; } @@ -346,8 +404,8 @@ export interface XYState { layers: LayerConfig[]; xTitle?: string; yTitle?: string; - showXAxisTitle?: boolean; - showYAxisTitle?: boolean; + yRightTitle?: string; + axisTitlesVisibilitySettings?: AxesSettingsConfig; tickLabelsVisibilitySettings?: AxesSettingsConfig; gridlinesVisibilitySettings?: AxesSettingsConfig; } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss index c353f3f370ee5..5b14fca78e65d 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss @@ -1,3 +1,3 @@ .lnsXyToolbar__popover { - width: 400px; -} + width: 320px; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx index 89a2574026ced..7e2e8f0453588 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx @@ -8,6 +8,8 @@ import React from 'react'; import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; import { EuiButtonGroupProps, EuiSuperSelect, EuiButtonGroup } from '@elastic/eui'; import { LayerContextMenu, XyToolbar } from './xy_config_panel'; +import { ToolbarPopover } from '../shared_components'; +import { AxisSettingsPopover } from './axis_settings_popover'; import { FramePublicAPI } from '../types'; import { State } from './types'; import { Position } from '@elastic/charts'; @@ -113,7 +115,7 @@ describe('XY Config panels', () => { expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry'); }); - it('should disable the select if there is no area or line series', () => { + it('should disable the popover if there is no area or line series', () => { const state = testState(); const component = shallow( { /> ); - expect(component.find(EuiSuperSelect).prop('disabled')).toEqual(true); + expect(component.find(ToolbarPopover).at(0).prop('isDisabled')).toEqual(true); }); - it('should show the values of the X and Y axes titles on the corresponding input text', () => { + it('should disable the popover if there is no right axis', () => { + const state = testState(); + const component = shallow(); + + expect(component.find(AxisSettingsPopover).at(2).prop('isDisabled')).toEqual(true); + }); + + it('should enable the popover if there is right axis', () => { const state = testState(); const component = shallow( { setState={jest.fn()} state={{ ...state, - xTitle: 'My custom X axis title', - yTitle: 'My custom Y axis title', + layers: [{ ...state.layers[0], yConfig: [{ axisMode: 'right', forAccessor: 'bar' }] }], }} /> ); - expect(component.find('[data-test-subj="lnsXAxisTitle"]').prop('value')).toBe( - 'My custom X axis title' - ); - expect(component.find('[data-test-subj="lnsYAxisTitle"]').prop('value')).toBe( - 'My custom Y axis title' - ); + expect(component.find(AxisSettingsPopover).at(2).prop('isDisabled')).toEqual(false); }); - it('should disable the input texts if the switch is off', () => { + it('should render the AxisSettingsPopover 3 times', () => { const state = testState(); const component = shallow( { setState={jest.fn()} state={{ ...state, - showXAxisTitle: false, - showYAxisTitle: false, + layers: [{ ...state.layers[0], yConfig: [{ axisMode: 'right', forAccessor: 'foo' }] }], }} /> ); - expect(component.find('[data-test-subj="lnsXAxisTitle"]').prop('disabled')).toBe(true); - expect(component.find('[data-test-subj="lnsYAxisTitle"]').prop('disabled')).toBe(true); - }); - - it('has the tick labels buttons enabled', () => { - const state = testState(); - const component = shallow(); - - const options = component - .find('[data-test-subj="lnsTickLabelsSettings"]') - .prop('options') as EuiButtonGroupProps['options']; - - expect(options!.map(({ label }) => label)).toEqual(['X-axis', 'Y-axis']); - - const selections = component - .find('[data-test-subj="lnsTickLabelsSettings"]') - .prop('idToSelectedMap'); - - expect(selections!).toEqual({ x: true, y: true }); - }); - - it('has the gridlines buttons enabled', () => { - const state = testState(); - const component = shallow(); - - const selections = component - .find('[data-test-subj="lnsGridlinesSettings"]') - .prop('idToSelectedMap'); - - expect(selections!).toEqual({ x: true, y: true }); + expect(component.find(AxisSettingsPopover).length).toEqual(3); }); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index 62fd6e013f20d..bc98bf53d9f12 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -5,7 +5,7 @@ */ import './xy_config_panel.scss'; -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { Position } from '@elastic/charts'; import { debounce } from 'lodash'; @@ -15,19 +15,13 @@ import { EuiFlexItem, EuiSuperSelect, EuiFormRow, - EuiPopover, EuiText, - EuiSelect, htmlIdGenerator, EuiForm, EuiColorPicker, EuiColorPickerProps, EuiToolTip, EuiIcon, - EuiFieldText, - EuiSwitch, - EuiHorizontalRule, - EuiTitle, } from '@elastic/eui'; import { VisualizationLayerWidgetProps, @@ -38,9 +32,13 @@ import { State, SeriesType, visualizationTypes, YAxisMode, AxesSettingsConfig } import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers'; import { trackUiEvent } from '../lens_ui_telemetry'; import { fittingFunctionDefinitions } from './fitting_functions'; -import { ToolbarButton } from '../toolbar_button'; +import { ToolbarPopover, LegendSettingsPopover } from '../shared_components'; +import { AxisSettingsPopover } from './axis_settings_popover'; +import { TooltipWrapper } from './tooltip_wrapper'; +import { getAxesConfiguration } from './axes_configuration'; type UnwrapArray = T extends Array ? P : T; +type AxesSettingsConfigKeys = keyof AxesSettingsConfig; function updateLayer(state: State, layer: UnwrapArray, index: number): State { const newLayers = [...state.layers]; @@ -57,21 +55,21 @@ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: id: `xy_legend_auto`, value: 'auto', label: i18n.translate('xpack.lens.xyChart.legendVisibility.auto', { - defaultMessage: 'auto', + defaultMessage: 'Auto', }), }, { id: `xy_legend_show`, value: 'show', label: i18n.translate('xpack.lens.xyChart.legendVisibility.show', { - defaultMessage: 'show', + defaultMessage: 'Show', }), }, { id: `xy_legend_hide`, value: 'hide', label: i18n.translate('xpack.lens.xyChart.legendVisibility.hide', { - defaultMessage: 'hide', + defaultMessage: 'Hide', }), }, ]; @@ -120,86 +118,25 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps) { } export function XyToolbar(props: VisualizationToolbarProps) { - const axes = [ - { - id: 'x', - label: 'X-axis', - }, - { - id: 'y', - label: 'Y-axis', - }, - ]; + const { state, setState } = props; - const { frame, state, setState } = props; - - const [open, setOpen] = useState(false); const hasNonBarSeries = state?.layers.some(({ seriesType }) => ['area_stacked', 'area', 'line'].includes(seriesType) ); - const [xAxisTitle, setXAxisTitle] = useState(state?.xTitle); - const [yAxisTitle, setYAxisTitle] = useState(state?.yTitle); - - const xyTitles = useCallback(() => { - const defaults = { - xTitle: xAxisTitle, - yTitle: yAxisTitle, - }; - const layer = state?.layers[0]; - if (!layer || !layer.accessors.length) { - return defaults; - } - const datasource = frame.datasourceLayers[layer.layerId]; - if (!datasource) { - return defaults; - } - const x = layer.xAccessor ? datasource.getOperationForColumnId(layer.xAccessor) : null; - const y = layer.accessors[0] ? datasource.getOperationForColumnId(layer.accessors[0]) : null; - - return { - xTitle: defaults.xTitle || x?.label, - yTitle: defaults.yTitle || y?.label, - }; - /* We want this callback to run only if open changes its state. What we want to accomplish here is to give the user a better UX. - By default these input fields have the axis legends. If the user changes the input text, the axis legends should also change. - BUT if the user cleans up the input text, it should remain empty until the user closes and reopens the panel. - In that case, the default axes legend should appear. */ - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [open]); - - useEffect(() => { - const { - xTitle, - yTitle, - }: { xTitle: string | undefined; yTitle: string | undefined } = xyTitles(); - setXAxisTitle(xTitle); - setYAxisTitle(yTitle); - }, [xyTitles]); - - const onXTitleChange = (value: string): void => { - setXAxisTitle(value); - setState({ ...state, xTitle: value }); - }; - - const onYTitleChange = (value: string): void => { - setYAxisTitle(value); - setState({ ...state, yTitle: value }); - }; - - type AxesSettingsConfigKeys = keyof AxesSettingsConfig; + const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false; + const axisGroups = getAxesConfiguration(state?.layers, shouldRotate); const tickLabelsVisibilitySettings = { x: state?.tickLabelsVisibilitySettings?.x ?? true, - y: state?.tickLabelsVisibilitySettings?.y ?? true, + yLeft: state?.tickLabelsVisibilitySettings?.yLeft ?? true, + yRight: state?.tickLabelsVisibilitySettings?.yRight ?? true, }; - - const onTickLabelsVisibilitySettingsChange = (optionId: string): void => { - const id = optionId as AxesSettingsConfigKeys; + const onTickLabelsVisibilitySettingsChange = (optionId: AxesSettingsConfigKeys): void => { const newTickLabelsVisibilitySettings = { ...tickLabelsVisibilitySettings, ...{ - [id]: !tickLabelsVisibilitySettings[id], + [optionId]: !tickLabelsVisibilitySettings[optionId], }, }; setState({ @@ -210,15 +147,15 @@ export function XyToolbar(props: VisualizationToolbarProps) { const gridlinesVisibilitySettings = { x: state?.gridlinesVisibilitySettings?.x ?? true, - y: state?.gridlinesVisibilitySettings?.y ?? true, + yLeft: state?.gridlinesVisibilitySettings?.yLeft ?? true, + yRight: state?.gridlinesVisibilitySettings?.yRight ?? true, }; - const onGridlinesVisibilitySettingsChange = (optionId: string): void => { - const id = optionId as AxesSettingsConfigKeys; + const onGridlinesVisibilitySettingsChange = (optionId: AxesSettingsConfigKeys): void => { const newGridlinesVisibilitySettings = { ...gridlinesVisibilitySettings, ...{ - [id]: !gridlinesVisibilitySettings[id], + [optionId]: !gridlinesVisibilitySettings[optionId], }, }; setState({ @@ -227,6 +164,27 @@ export function XyToolbar(props: VisualizationToolbarProps) { }); }; + const axisTitlesVisibilitySettings = { + x: state?.axisTitlesVisibilitySettings?.x ?? true, + yLeft: state?.axisTitlesVisibilitySettings?.yLeft ?? true, + yRight: state?.axisTitlesVisibilitySettings?.yRight ?? true, + }; + const onAxisTitlesVisibilitySettingsChange = ( + axis: AxesSettingsConfigKeys, + checked: boolean + ): void => { + const newAxisTitlesVisibilitySettings = { + ...axisTitlesVisibilitySettings, + ...{ + [axis]: checked, + }, + }; + setState({ + ...state, + axisTitlesVisibilitySettings: newAxisTitlesVisibilitySettings, + }); + }; + const legendMode = state?.legend.isVisible && !state?.legend.showSingleSeries ? 'auto' @@ -234,243 +192,149 @@ export function XyToolbar(props: VisualizationToolbarProps) { ? 'hide' : 'show'; return ( - - - { - setOpen(!open); - }} - > - {i18n.translate('xpack.lens.xyChart.settingsLabel', { defaultMessage: 'Settings' })} - - } - isOpen={open} - closePopover={() => { - setOpen(false); - }} - anchorPosition="downRight" - > - + + + - - { - return { - value: id, - dropdownDisplay: ( - <> - {title} - -

{description}

-
- - ), - inputDisplay: title, - }; + setState({ ...state, fittingFunction: value })} - itemLayoutAlign="top" - hasDividers - /> - -
- - - value === legendMode)!.id} - onChange={(optionId) => { - const newMode = legendOptions.find(({ id }) => id === optionId)!.value; - if (newMode === 'auto') { - setState({ - ...state, - legend: { ...state.legend, isVisible: true, showSingleSeries: false }, - }); - } else if (newMode === 'show') { - setState({ - ...state, - legend: { ...state.legend, isVisible: true, showSingleSeries: true }, - }); - } else if (newMode === 'hide') { - setState({ - ...state, - legend: { ...state.legend, isVisible: false, showSingleSeries: false }, - }); - } - }} - /> - - - { + > + { + return { + value: id, + dropdownDisplay: ( + <> + {title} + +

{description}

+
+ + ), + inputDisplay: title, + }; + })} + valueOfSelected={state?.fittingFunction || 'None'} + onChange={(value) => setState({ ...state, fittingFunction: value })} + itemLayoutAlign="top" + hasDividers + /> +
+ + + { + const newMode = legendOptions.find(({ id }) => id === optionId)!.value; + if (newMode === 'auto') { setState({ ...state, - legend: { ...state.legend, position: e.target.value as Position }, + legend: { ...state.legend, isVisible: true, showSingleSeries: false }, }); - }} - /> - - - - onTickLabelsVisibilitySettingsChange(id)} - buttonSize="compressed" - isFullWidth - type="multi" - /> - - { + setState({ + ...state, + legend: { ...state.legend, position: id as Position }, + }); + }} + /> +
+
+ + + - onGridlinesVisibilitySettingsChange(id)} - buttonSize="compressed" - isFullWidth - type="multi" - /> - - - - - {i18n.translate('xpack.lens.xyChart.axisTitles', { defaultMessage: 'Axis titles' })} - - - - X-axis - - - setState({ ...state, showXAxisTitle: target.checked }) - } - checked={state?.showXAxisTitle ?? true} - /> - - + condition={ + Object.keys(axisGroups.find((group) => group.groupId === 'left') || {}).length === 0 } > - onXTitleChange(target.value)} - aria-label={i18n.translate('xpack.lens.xyChart.overwriteXaxis', { - defaultMessage: 'Overwrite X-axis title', - })} + setState({ ...state, yTitle: value })} + areTickLabelsVisible={tickLabelsVisibilitySettings.yLeft} + toggleTickLabelsVisibility={onTickLabelsVisibilitySettingsChange} + areGridlinesVisible={gridlinesVisibilitySettings.yLeft} + toggleGridlinesVisibility={onGridlinesVisibilitySettingsChange} + isDisabled={ + Object.keys(axisGroups.find((group) => group.groupId === 'left') || {}).length === 0 + } + isAxisTitleVisible={axisTitlesVisibilitySettings.yLeft} + toggleAxisTitleVisibility={onAxisTitlesVisibilitySettingsChange} /> - - - Y-axis - - - setState({ ...state, showYAxisTitle: target.checked }) - } - checked={state?.showYAxisTitle ?? true} - /> - -
+ + setState({ ...state, xTitle: value })} + areTickLabelsVisible={tickLabelsVisibilitySettings.x} + toggleTickLabelsVisibility={onTickLabelsVisibilitySettingsChange} + areGridlinesVisible={gridlinesVisibilitySettings.x} + toggleGridlinesVisibility={onGridlinesVisibilitySettingsChange} + isAxisTitleVisible={axisTitlesVisibilitySettings.x} + toggleAxisTitleVisibility={onAxisTitlesVisibilitySettingsChange} + /> + group.groupId === 'right') || {}).length === 0 } > - onYTitleChange(target.value)} - aria-label={i18n.translate('xpack.lens.xyChart.overwriteYaxis', { - defaultMessage: 'Overwrite Y-axis title', - })} + setState({ ...state, yRightTitle: value })} + areTickLabelsVisible={tickLabelsVisibilitySettings.yRight} + toggleTickLabelsVisibility={onTickLabelsVisibilitySettingsChange} + areGridlinesVisible={gridlinesVisibilitySettings.yRight} + toggleGridlinesVisibility={onGridlinesVisibilitySettingsChange} + isDisabled={ + Object.keys(axisGroups.find((group) => group.groupId === 'right') || {}).length === + 0 + } + isAxisTitleVisible={axisTitlesVisibilitySettings.yRight} + toggleAxisTitleVisibility={onAxisTitlesVisibilitySettingsChange} /> - - + + ); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx index c9c27193c437e..1d809f222eb00 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx @@ -215,22 +215,29 @@ const sampleLayer: LayerArgs = { const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({ xTitle: '', yTitle: '', + yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top, }, - showXAxisTitle: true, - showYAxisTitle: true, + axisTitlesVisibilitySettings: { + type: 'lens_xy_axisTitlesVisibilityConfig', + x: true, + yLeft: true, + yRight: true, + }, tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, - y: false, + yLeft: false, + yRight: false, }, gridlinesVisibilitySettings: { type: 'lens_xy_gridlinesConfig', x: true, - y: false, + yLeft: false, + yRight: false, }, layers, }); @@ -291,7 +298,8 @@ describe('xy_expression', () => { test('tickLabelsConfig produces the correct arguments', () => { const args: AxesSettingsConfig = { x: true, - y: false, + yLeft: false, + yRight: false, }; const result = tickLabelsConfig.fn(null, args, createMockExecutionContext()); @@ -305,7 +313,8 @@ describe('xy_expression', () => { test('gridlinesConfig produces the correct arguments', () => { const args: AxesSettingsConfig = { x: true, - y: false, + yLeft: false, + yRight: false, }; const result = gridlinesConfig.fn(null, args, createMockExecutionContext()); @@ -1417,7 +1426,12 @@ describe('xy_expression', () => { test('it should set the tickLabel visibility on the x axis if the tick labels is hidden', () => { const { data, args } = sampleArgs(); - args.tickLabelsVisibilitySettings = { x: false, y: true, type: 'lens_xy_tickLabelsConfig' }; + args.tickLabelsVisibilitySettings = { + x: false, + yLeft: true, + yRight: true, + type: 'lens_xy_tickLabelsConfig', + }; const instance = shallow( { test('it should set the tickLabel visibility on the y axis if the tick labels is hidden', () => { const { data, args } = sampleArgs(); - args.tickLabelsVisibilitySettings = { x: true, y: false, type: 'lens_xy_tickLabelsConfig' }; + args.tickLabelsVisibilitySettings = { + x: true, + yLeft: false, + yRight: false, + type: 'lens_xy_tickLabelsConfig', + }; const instance = shallow( { test('it should set the tickLabel visibility on the x axis if the tick labels is shown', () => { const { data, args } = sampleArgs(); - args.tickLabelsVisibilitySettings = { x: true, y: true, type: 'lens_xy_tickLabelsConfig' }; + args.tickLabelsVisibilitySettings = { + x: true, + yLeft: true, + yRight: true, + type: 'lens_xy_tickLabelsConfig', + }; const instance = shallow( { test('it should set the tickLabel visibility on the y axis if the tick labels is shown', () => { const { data, args } = sampleArgs(); - args.tickLabelsVisibilitySettings = { x: false, y: true, type: 'lens_xy_tickLabelsConfig' }; + args.tickLabelsVisibilitySettings = { + x: false, + yLeft: true, + yRight: true, + type: 'lens_xy_tickLabelsConfig', + }; const instance = shallow( { const args: XYArgs = { xTitle: '', yTitle: '', + yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top }, tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, - y: true, + yLeft: true, + yRight: true, }, gridlinesVisibilitySettings: { type: 'lens_xy_gridlinesConfig', x: true, - y: false, + yLeft: false, + yRight: false, }, layers: [ { @@ -1635,16 +1667,19 @@ describe('xy_expression', () => { const args: XYArgs = { xTitle: '', yTitle: '', + yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top }, tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, - y: false, + yLeft: false, + yRight: false, }, gridlinesVisibilitySettings: { type: 'lens_xy_gridlinesConfig', x: true, - y: false, + yLeft: false, + yRight: false, }, layers: [ { @@ -1701,16 +1736,19 @@ describe('xy_expression', () => { const args: XYArgs = { xTitle: '', yTitle: '', + yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top }, tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, - y: false, + yLeft: false, + yRight: false, }, gridlinesVisibilitySettings: { type: 'lens_xy_gridlinesConfig', x: true, - y: false, + yLeft: false, + yRight: false, }, layers: [ { @@ -1894,7 +1932,12 @@ describe('xy_expression', () => { test('it should hide the X axis title if the corresponding switch is off', () => { const { data, args } = sampleArgs(); - args.showXAxisTitle = false; + args.axisTitlesVisibilitySettings = { + x: false, + yLeft: true, + yRight: true, + type: 'lens_xy_axisTitlesVisibilityConfig', + }; const component = shallow( { test('it should show the X axis gridlines if the setting is on', () => { const { data, args } = sampleArgs(); - args.gridlinesVisibilitySettings = { x: true, y: false, type: 'lens_xy_gridlinesConfig' }; + args.gridlinesVisibilitySettings = { + x: true, + yLeft: false, + yRight: false, + type: 'lens_xy_gridlinesConfig', + }; const component = shallow( , - index: number + groupId: string ) => { - if (index > 0 && args.yTitle) return; + const yTitle = groupId === 'right' ? args.yRightTitle : args.yTitle; return ( - args.yTitle || + yTitle || axisSeries .map( (series) => @@ -322,6 +333,24 @@ export function XYChart({ ); }; + const getYAxesStyle = (groupId: string) => { + const style = { + tickLabel: { + visible: + groupId === 'right' + ? tickLabelsVisibilitySettings?.yRight + : tickLabelsVisibilitySettings?.yLeft, + }, + axisTitle: { + visible: + groupId === 'right' + ? axisTitlesVisibilitySettings?.yRight + : axisTitlesVisibilitySettings?.yLeft, + }, + }; + return style; + }; + return ( - {yAxesConfiguration.map((axis, index) => ( + {yAxesConfiguration.map((axis) => ( axis.formatter.convert(d)} - style={{ - tickLabel: { - visible: tickLabelsVisibilitySettings?.y, - }, - axisTitle: { - visible: showYAxisTitle, - }, - }} + tickFormat={(d) => axis.formatter?.convert(d) || ''} + style={getYAxesStyle(axis.groupId)} /> ))} diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts index ea5cff80695a3..09a2cc652a9b3 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts @@ -555,10 +555,9 @@ describe('xy_suggestions', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, fittingFunction: 'None', - showXAxisTitle: true, - showYAxisTitle: true, - gridlinesVisibilitySettings: { x: true, y: true }, - tickLabelsVisibilitySettings: { x: true, y: false }, + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false }, preferredSeriesType: 'bar', layers: [ { @@ -597,10 +596,9 @@ describe('xy_suggestions', () => { legend: { isVisible: true, position: 'bottom' }, preferredSeriesType: 'bar', fittingFunction: 'None', - showXAxisTitle: true, - showYAxisTitle: true, - gridlinesVisibilitySettings: { x: true, y: true }, - tickLabelsVisibilitySettings: { x: true, y: false }, + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false }, layers: [ { accessors: ['price', 'quantity'], @@ -710,10 +708,9 @@ describe('xy_suggestions', () => { legend: { isVisible: true, position: 'bottom' }, preferredSeriesType: 'bar', fittingFunction: 'None', - showXAxisTitle: true, - showYAxisTitle: true, - gridlinesVisibilitySettings: { x: true, y: true }, - tickLabelsVisibilitySettings: { x: true, y: false }, + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false }, layers: [ { accessors: ['price', 'quantity'], @@ -753,10 +750,9 @@ describe('xy_suggestions', () => { legend: { isVisible: true, position: 'bottom' }, preferredSeriesType: 'bar', fittingFunction: 'None', - showXAxisTitle: true, - showYAxisTitle: true, - gridlinesVisibilitySettings: { x: true, y: true }, - tickLabelsVisibilitySettings: { x: true, y: false }, + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false }, layers: [ { accessors: ['price'], @@ -797,10 +793,9 @@ describe('xy_suggestions', () => { legend: { isVisible: true, position: 'bottom' }, preferredSeriesType: 'bar', fittingFunction: 'None', - showXAxisTitle: true, - showYAxisTitle: true, - gridlinesVisibilitySettings: { x: true, y: true }, - tickLabelsVisibilitySettings: { x: true, y: false }, + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false }, layers: [ { accessors: ['price', 'quantity'], diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index 42fc538874b93..e6286523d8e2e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -489,15 +489,21 @@ function buildSuggestion({ fittingFunction: currentState?.fittingFunction || 'None', xTitle: currentState?.xTitle, yTitle: currentState?.yTitle, - showXAxisTitle: currentState?.showXAxisTitle ?? true, - showYAxisTitle: currentState?.showYAxisTitle ?? true, + yRightTitle: currentState?.yRightTitle, + axisTitlesVisibilitySettings: currentState?.axisTitlesVisibilitySettings || { + x: true, + yLeft: true, + yRight: true, + }, tickLabelsVisibilitySettings: currentState?.tickLabelsVisibilitySettings || { x: true, - y: true, + yLeft: true, + yRight: true, }, gridlinesVisibilitySettings: currentState?.gridlinesVisibilitySettings || { x: true, - y: true, + yLeft: true, + yRight: true, }, preferredSeriesType: seriesType, layers: Object.keys(existingLayer).length ? keptLayers : [...keptLayers, newLayer], diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 68f6bc166cd1d..e54d6739c0600 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9684,8 +9684,6 @@ "xpack.lens.pieChart.fitInsideOnlyLabel": "内部のみ", "xpack.lens.pieChart.hiddenNumbersLabel": "グラフから非表示", "xpack.lens.pieChart.labelPositionLabel": "ラベル位置", - "xpack.lens.pieChart.legendDisplayLabel": "凡例表示", - "xpack.lens.pieChart.legendDisplayLegend": "凡例表示", "xpack.lens.pieChart.nestedLegendLabel": "ネストされた凡例", "xpack.lens.pieChart.numberLabels": "ラベル値", "xpack.lens.pieChart.showCategoriesLabel": "内部または外部", @@ -9711,7 +9709,6 @@ "xpack.lens.xyChart.chartTypeLegend": "チャートタイプ", "xpack.lens.xyChart.fittingDisabledHelpText": "この設定は折れ線グラフとエリアグラフでのみ適用されます。", "xpack.lens.xyChart.fittingFunction.help": "欠測値の処理方法を定義", - "xpack.lens.xyChart.fittingLabel": "欠測値を埋める", "xpack.lens.xyChart.help": "X/Y チャート", "xpack.lens.xyChart.isVisible.help": "判例の表示・非表示を指定します。", "xpack.lens.xyChart.legend.help": "チャートの凡例を構成します。", @@ -9720,7 +9717,6 @@ "xpack.lens.xyChart.renderer.help": "X/Y チャートを再レンダリング", "xpack.lens.xyChart.seriesColor.auto": "自動", "xpack.lens.xyChart.seriesColor.label": "系列色", - "xpack.lens.xyChart.settingsLabel": "設定", "xpack.lens.xyChart.splitSeries": "系列を分割", "xpack.lens.xyChart.title.help": "軸のタイトル", "xpack.lens.xyChart.xAxisLabel": "X 軸", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index cb43cefdc3655..4c8ccd56c1c01 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9690,8 +9690,6 @@ "xpack.lens.pieChart.fitInsideOnlyLabel": "仅内部", "xpack.lens.pieChart.hiddenNumbersLabel": "在图表中隐藏", "xpack.lens.pieChart.labelPositionLabel": "标签位置", - "xpack.lens.pieChart.legendDisplayLabel": "图例显示", - "xpack.lens.pieChart.legendDisplayLegend": "图例显示", "xpack.lens.pieChart.nestedLegendLabel": "嵌套图例", "xpack.lens.pieChart.numberLabels": "标签值", "xpack.lens.pieChart.showCategoriesLabel": "内部或外部", @@ -9717,7 +9715,6 @@ "xpack.lens.xyChart.chartTypeLegend": "图表类型", "xpack.lens.xyChart.fittingDisabledHelpText": "此设置仅适用于折线图和非堆叠面积图。", "xpack.lens.xyChart.fittingFunction.help": "定义处理缺失值的方式", - "xpack.lens.xyChart.fittingLabel": "填充缺失值", "xpack.lens.xyChart.help": "X/Y 图表", "xpack.lens.xyChart.isVisible.help": "指定图例是否可见。", "xpack.lens.xyChart.legend.help": "配置图表图例。", @@ -9726,7 +9723,6 @@ "xpack.lens.xyChart.renderer.help": "X/Y 图表呈现器", "xpack.lens.xyChart.seriesColor.auto": "自动", "xpack.lens.xyChart.seriesColor.label": "系列颜色", - "xpack.lens.xyChart.settingsLabel": "设置", "xpack.lens.xyChart.splitSeries": "拆分序列", "xpack.lens.xyChart.title.help": "轴标题", "xpack.lens.xyChart.xAxisLabel": "X 轴", From 89af7e676d36ff92da9e013ce38769523185ce95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 16 Sep 2020 09:40:58 +0100 Subject: [PATCH 13/15] showing service maps when filte by environment not defined (#77483) --- .../get_service_map_from_trace_ids.ts | 3 +- .../lib/service_map/get_trace_sample_ids.ts | 5 +- .../trial/tests/service_maps/service_maps.ts | 156 ++++++++++++++++++ 3 files changed, 160 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts index f6e331a09fa65..a7771d14532d6 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { find, uniqBy } from 'lodash'; +import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { SERVICE_ENVIRONMENT, SERVICE_NAME, @@ -35,7 +36,7 @@ export function getConnections( SERVICE_NAME in node && (node as ServiceConnectionNode)[SERVICE_NAME] === serviceName; } - if (environment) { + if (environment && environment !== ENVIRONMENT_NOT_DEFINED.value) { matches = matches && SERVICE_ENVIRONMENT in node && diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index d6d681f24ab85..dfc4e02c25a7f 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -14,6 +14,7 @@ import { TRACE_ID, SPAN_DESTINATION_SERVICE_RESOURCE, } from '../../../common/elasticsearch_fieldnames'; +import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; const MAX_TRACES_TO_INSPECT = 1000; @@ -47,9 +48,7 @@ export async function getTraceSampleIds({ query.bool.filter.push({ term: { [SERVICE_NAME]: serviceName } }); } - if (environment) { - query.bool.filter.push({ term: { [SERVICE_ENVIRONMENT]: environment } }); - } + query.bool.filter.push(...getEnvironmentUiFilterES(environment)); const fingerprintBucketSize = serviceName ? config['xpack.apm.serviceMapFingerprintBucketSize'] diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts index 4d9d4ef9f2cad..50f43c7f35446 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts @@ -84,6 +84,162 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) expectSnapshot(elements).toMatch(); }); + + it('returns service map elements filtering by environment not defined', async () => { + const start = encodeURIComponent('2020-06-28T10:24:46.055Z'); + const end = encodeURIComponent('2020-06-29T10:24:46.055Z'); + const environment = 'ENVIRONMENT_NOT_DEFINED'; + const response = await supertest.get( + `/api/apm/service-map?start=${start}&end=${end}&environment=${environment}` + ); + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` + Object { + "elements": Array [ + Object { + "data": Object { + "id": "client~opbeans-node", + "source": "client", + "sourceData": Object { + "agent.name": "rum-js", + "id": "client", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "client", + }, + "target": "opbeans-node", + "targetData": Object { + "agent.name": "nodejs", + "id": "opbeans-node", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "opbeans-node", + }, + }, + }, + Object { + "data": Object { + "id": "opbeans-java~>postgresql", + "source": "opbeans-java", + "sourceData": Object { + "agent.name": "java", + "id": "opbeans-java", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "opbeans-java", + }, + "target": ">postgresql", + "targetData": Object { + "id": ">postgresql", + "label": "postgresql", + "span.destination.service.resource": "postgresql", + "span.subtype": "postgresql", + "span.type": "db", + }, + }, + }, + Object { + "data": Object { + "id": "opbeans-node~>postgresql", + "source": "opbeans-node", + "sourceData": Object { + "agent.name": "nodejs", + "id": "opbeans-node", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "opbeans-node", + }, + "target": ">postgresql", + "targetData": Object { + "id": ">postgresql", + "label": "postgresql", + "span.destination.service.resource": "postgresql", + "span.subtype": "postgresql", + "span.type": "db", + }, + }, + }, + Object { + "data": Object { + "id": "opbeans-node~>redis", + "source": "opbeans-node", + "sourceData": Object { + "agent.name": "nodejs", + "id": "opbeans-node", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "opbeans-node", + }, + "target": ">redis", + "targetData": Object { + "id": ">redis", + "label": "redis", + "span.destination.service.resource": "redis", + "span.subtype": "redis", + "span.type": "cache", + }, + }, + }, + Object { + "data": Object { + "id": "opbeans-node~opbeans-java", + "source": "opbeans-node", + "sourceData": Object { + "agent.name": "nodejs", + "id": "opbeans-node", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "opbeans-node", + }, + "target": "opbeans-java", + "targetData": Object { + "agent.name": "java", + "id": "opbeans-java", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "opbeans-java", + }, + }, + }, + Object { + "data": Object { + "agent.name": "rum-js", + "id": "client", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "client", + }, + }, + Object { + "data": Object { + "agent.name": "nodejs", + "id": "opbeans-node", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "opbeans-node", + }, + }, + Object { + "data": Object { + "id": ">redis", + "label": "redis", + "span.destination.service.resource": "redis", + "span.subtype": "redis", + "span.type": "cache", + }, + }, + Object { + "data": Object { + "agent.name": "java", + "id": "opbeans-java", + "service.environment": "ENVIRONMENT_NOT_DEFINED", + "service.name": "opbeans-java", + }, + }, + Object { + "data": Object { + "id": ">postgresql", + "label": "postgresql", + "span.destination.service.resource": "postgresql", + "span.subtype": "postgresql", + "span.type": "db", + }, + }, + ], + } + `); + }); }); }); From b162cca68d59e085067c6a0d5acecc8075f3df1c Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 16 Sep 2020 09:47:02 +0100 Subject: [PATCH 14/15] [ML] Fixing field caps wrapper endpoint (#77546) --- x-pack/plugins/ml/server/routes/indices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/server/routes/indices.ts b/x-pack/plugins/ml/server/routes/indices.ts index ee817c492dbd4..8a5df1415bde8 100644 --- a/x-pack/plugins/ml/server/routes/indices.ts +++ b/x-pack/plugins/ml/server/routes/indices.ts @@ -40,7 +40,7 @@ export function indicesRoutes({ router, mlLicense }: RouteInitialization) { requestFields !== undefined && Array.isArray(requestFields) ? requestFields.join(',') : '*'; - const { body } = await client.asInternalUser.fieldCaps({ index, fields }); + const { body } = await client.asCurrentUser.fieldCaps({ index, fields }); return response.ok({ body }); } catch (e) { return response.customError(wrapError(e)); From 3c555e2ca256b0601bf89345a85eb79df20740e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Wed, 16 Sep 2020 11:30:51 +0200 Subject: [PATCH 15/15] Collapse alert chart previews by default (#77479) --- .../logs/expression_editor/criteria.tsx | 30 +++++++++++-------- .../logs/expression_editor/criterion.tsx | 15 ++++++++-- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criteria.tsx b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criteria.tsx index 7a4b2ecaa5e43..627ea2bbef429 100644 --- a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criteria.tsx +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criteria.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexItem, EuiFlexGroup, EuiAccordion } from '@elastic/eui'; import { IFieldType } from 'src/plugins/data/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { IErrorObject } from '../../../../../../triggers_actions_ui/public/types'; @@ -44,23 +44,29 @@ export const Criteria: React.FC = ({ {criteria.map((criterion, idx) => { return ( - - 1} - errors={errors[idx.toString()] as IErrorObject} - /> + 1} + errors={errors[idx.toString()] as IErrorObject} + /> + } + key={idx} + arrowDisplay="right" + > - + ); })} diff --git a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criterion.tsx b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criterion.tsx index 66bde380a6d9e..9ee9373bd2c14 100644 --- a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criterion.tsx +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criterion.tsx @@ -162,7 +162,10 @@ export const Criterion: React.FC = ({ value={criterion.field} isActive={isFieldPopoverOpen} color={errors.field.length === 0 ? 'secondary' : 'danger'} - onClick={() => setIsFieldPopoverOpen(true)} + onClick={(e) => { + e.stopPropagation(); + setIsFieldPopoverOpen(true); + }} /> } isOpen={isFieldPopoverOpen} @@ -202,7 +205,10 @@ export const Criterion: React.FC = ({ ? 'secondary' : 'danger' } - onClick={() => setIsComparatorPopoverOpen(true)} + onClick={(e) => { + e.stopPropagation(); + setIsComparatorPopoverOpen(true); + }} /> } isOpen={isComparatorPopoverOpen} @@ -260,7 +266,10 @@ export const Criterion: React.FC = ({ })} color={'danger'} iconType={'trash'} - onClick={() => removeCriterion(idx)} + onClick={(e: React.MouseEvent) => { + e.stopPropagation(); + removeCriterion(idx); + }} /> )}