From 77a6caa704d53935eeef5c7c421521da164d3749 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Thu, 5 Aug 2021 22:58:57 -0500 Subject: [PATCH] [index patterns] index pattern create modal (#101853) index pattern creation flyout --- .i18nrc.json | 1 + docs/developer/plugin-list.asciidoc | 4 + ...verlayflyoutopenoptions.hideclosebutton.md | 11 + ...in-core-public.overlayflyoutopenoptions.md | 1 + ...ns-data-public.aggregationrestrictions.md} | 4 +- ...ta-public.getfieldsoptions.allownoindex.md | 11 + ...s-data-public.getfieldsoptions.lookback.md | 11 + ...in-plugins-data-public.getfieldsoptions.md | 23 + ...data-public.getfieldsoptions.metafields.md | 11 + ...ns-data-public.getfieldsoptions.pattern.md | 11 + ...ata-public.getfieldsoptions.rollupindex.md | 11 + ...ugins-data-public.getfieldsoptions.type.md | 11 + .../kibana-plugin-plugins-data-public.md | 3 +- packages/kbn-optimizer/limits.yml | 2 +- .../public/overlays/flyout/flyout_service.tsx | 1 + src/core/public/public.api.md | 2 + .../index_patterns/index_patterns.ts | 2 +- src/plugins/data/public/index.ts | 2 + src/plugins/data/public/public.api.md | 77 ++- src/plugins/index_pattern_editor/README.md | 48 ++ .../index_pattern_editor/jest.config.js | 13 + src/plugins/index_pattern_editor/kibana.json | 13 + .../public/components/_templates.scss | 11 + .../public/components/_variables.scss | 1 + .../advanced_params_content.tsx | 66 +++ .../advanced_params_section.tsx | 44 ++ .../advanced_params_content/index.ts | 9 + .../empty_index_list_prompt.test.tsx.snap} | 76 +-- .../empty_index_list_prompt.scss} | 4 +- .../empty_index_list_prompt.test.tsx} | 27 +- .../empty_index_list_prompt.tsx} | 67 ++- .../empty_index_list_prompt/index.ts | 9 + .../empty_index_pattern_prompt.test.tsx.snap | 42 +- .../assets/index_pattern_illustration.scss | 0 .../assets/index_pattern_illustration.tsx | 3 +- .../empty_index_pattern_prompt.scss | 4 +- .../empty_index_pattern_prompt.test.tsx | 7 +- .../empty_index_pattern_prompt.tsx | 59 +-- .../empty_index_pattern_prompt/index.tsx | 0 .../empty_prompts/empty_prompts.tsx | 92 ++++ .../components/empty_prompts/index.tsx} | 2 +- .../empty_prompts/prompt_footer}/index.ts | 2 +- .../prompt_footer/prompt_footer.tsx | 42 ++ .../components/flyout_panels/flyout_panel.tsx | 120 +++++ .../flyout_panels/flyout_panels.scss | 42 ++ .../flyout_panels/flyout_panels.tsx | 119 +++++ .../flyout_panels/flyout_panels_content.tsx} | 19 +- .../flyout_panels/flyout_panels_footer.tsx | 23 + .../flyout_panels/flyout_panels_header.tsx} | 18 +- .../public/components/flyout_panels/index.ts | 21 + .../public/components/footer/footer.tsx | 63 +++ .../public/components/footer}/index.ts | 2 +- .../public/components/form_fields/index.ts | 11 + .../form_fields/timestamp_field.tsx | 157 ++++++ .../components/form_fields/title_field.tsx | 232 ++++++++ .../components/form_fields/type_field.tsx | 114 ++++ .../public/components/form_schema.ts | 63 +++ .../public/components/i18n_texts.ts | 26 + .../public/components/index.ts | 24 + .../components/index_pattern_editor.scss | 13 + .../components/index_pattern_editor.tsx | 43 ++ .../index_pattern_editor_flyout_content.tsx | 379 +++++++++++++ .../components/index_pattern_editor_lazy.tsx | 22 + ...index_pattern_flyout_content_container.tsx | 56 ++ .../loading_indices.test.tsx.snap | 2 +- .../components/loading_indices/index.ts | 0 .../loading_indices/loading_indices.test.tsx | 0 .../loading_indices/loading_indices.tsx | 2 +- .../public/components/preview_panel}/index.ts | 2 +- .../__snapshots__/indices_list.test.tsx.snap | 10 +- .../preview_panel}/indices_list/index.ts | 0 .../indices_list/indices_list.test.tsx | 2 +- .../indices_list/indices_list.tsx | 7 +- .../preview_panel/preview_panel.tsx | 49 ++ .../status_message.test.tsx.snap | 27 +- .../preview_panel}/status_message/index.ts | 0 .../status_message/status_message.test.tsx | 5 +- .../status_message/status_message.tsx | 101 ++-- .../components/rollup_beta_warning/index.ts | 9 + .../rollup_beta_warning.tsx | 42 ++ .../index_pattern_editor/public/constants.ts | 11 + .../index_pattern_editor/public/index.ts | 27 + .../__snapshots__/get_indices.test.ts.snap | 0 .../public}/lib/can_append_wildcard.test.ts | 0 .../public}/lib/can_append_wildcard.ts | 0 .../lib/contains_illegal_characters.ts | 0 .../lib/contains_invalid_characters.test.ts | 0 .../public}/lib/ensure_minimum_time.test.ts | 13 +- .../public}/lib/ensure_minimum_time.ts | 4 +- .../public}/lib/extract_time_fields.test.ts | 8 +- .../public/lib/extract_time_fields.ts | 44 ++ .../public}/lib/get_indices.test.ts | 15 +- .../public}/lib/get_indices.ts | 34 +- .../public}/lib/get_matched_indices.test.ts | 0 .../public}/lib/get_matched_indices.ts | 8 +- .../public}/lib/index.ts | 0 .../index_pattern_editor/public/mocks.ts | 32 ++ .../public/open_editor.tsx | 92 ++++ .../public/plugin.test.tsx | 83 +++ .../index_pattern_editor/public/plugin.tsx | 76 +++ .../public/shared_imports.ts | 50 ++ .../public/test_utils/helpers.ts | 41 ++ .../public/test_utils/index.ts | 13 + .../public/test_utils/mocks.ts | 17 + .../public/test_utils/test_utils.ts | 11 + .../index_pattern_editor/public/types.ts | 184 +++++++ .../index_pattern_editor/tsconfig.json | 20 + .../index_pattern_management/kibana.json | 2 +- .../__snapshots__/utils.test.ts.snap | 9 +- .../create_button/create_button.tsx | 137 ----- .../CREATE_INDEX_PATTERN.md | 15 - .../create_index_pattern_wizard.test.tsx.snap | 250 --------- .../header/__snapshots__/header.test.tsx.snap | 499 ------------------ .../components/header/header.test.tsx | 71 --- .../components/header/header.tsx | 88 --- .../__snapshots__/loading_state.test.tsx.snap | 39 -- .../loading_state/loading_state.tsx | 32 -- .../step_index_pattern.test.tsx.snap | 58 -- .../header/__snapshots__/header.test.tsx.snap | 221 -------- .../components/header/header.test.tsx | 49 -- .../components/header/header.tsx | 144 ----- .../components/step_index_pattern/index.ts | 9 - .../step_index_pattern.test.tsx | 244 --------- .../step_index_pattern/step_index_pattern.tsx | 367 ------------- .../step_time_field.test.tsx.snap | 364 ------------- .../action_buttons/action_buttons.tsx | 47 -- .../components/action_buttons/index.ts | 9 - .../advanced_options.test.tsx.snap | 69 --- .../advanced_options.test.tsx | 39 -- .../advanced_options/advanced_options.tsx | 80 --- .../components/advanced_options/index.ts | 9 - .../header/__snapshots__/header.test.tsx.snap | 34 -- .../components/header/header.tsx | 42 -- .../components/header/index.ts | 9 - .../__snapshots__/time_field.test.tsx.snap | 188 ------- .../components/time_field/index.ts | 9 - .../components/time_field/time_field.test.tsx | 73 --- .../components/time_field/time_field.tsx | 116 ---- .../components/step_time_field/index.ts | 9 - .../step_time_field/step_time_field.test.tsx | 331 ------------ .../step_time_field/step_time_field.tsx | 262 --------- .../constants/index.ts | 17 - .../create_index_pattern_wizard.test.tsx | 162 ------ .../create_index_pattern_wizard.tsx | 284 ---------- .../create_index_pattern_wizard/index.ts | 9 - .../lib/extract_time_fields.ts | 53 -- .../create_index_pattern_wizard/types.ts | 59 --- .../edit_index_pattern/edit_index_pattern.tsx | 11 +- .../indexed_fields_table.test.tsx | 4 +- .../edit_index_pattern/tabs/tabs.tsx | 21 +- .../edit_index_pattern/tabs/utils.ts | 10 +- .../public/components/index.ts | 1 - .../index_pattern_table/empty_state/index.ts | 9 - .../index_pattern_table.tsx | 108 ++-- .../public/components/utils.test.ts | 9 +- .../public/components/utils.ts | 102 +++- .../index_pattern_management/public/index.ts | 6 - .../mount_management_section.tsx | 6 +- .../index_pattern_management/public/mocks.ts | 23 +- .../index_pattern_management/public/plugin.ts | 29 +- .../components/rollup_prompt/index.ts | 9 - .../rollup_prompt/rollup_prompt.tsx | 40 -- .../public/service/creation/config.ts | 112 ---- .../public/service/creation/index.ts | 12 - .../public/service/creation/manager.ts | 70 --- .../creation/rollup_creation_config.js | 169 ------ .../public/service/index.ts | 11 - .../index_pattern_management_service.ts | 48 -- .../public/service/list/config.ts | 45 -- .../public/service/list/index.ts | 12 - .../public/service/list/manager.ts | 55 -- .../public/service/list/rollup_list_config.ts | 62 --- .../index_pattern_management/public/types.ts | 2 + .../index_pattern_management/tsconfig.json | 1 + test/accessibility/apps/management.ts | 1 + .../_create_index_pattern_wizard.js | 19 - .../_index_pattern_create_delete.js | 25 + test/functional/page_objects/settings_page.ts | 73 +-- tsconfig.json | 1 + tsconfig.refs.json | 1 + .../translations/translations/ja-JP.json | 76 --- .../translations/translations/zh-CN.json | 82 --- .../index_patterns_security.ts | 2 +- .../management/create_index_pattern_wizard.js | 2 +- .../apps/rollup_job/hybrid_index_pattern.js | 5 - 185 files changed, 3398 insertions(+), 5832 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.overlayflyoutopenoptions.hideclosebutton.md rename docs/development/plugins/data/public/{kibana-plugin-plugins-data-public.indexpatternaggrestrictions.md => kibana-plugin-plugins-data-public.aggregationrestrictions.md} (69%) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.allownoindex.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.lookback.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.metafields.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.pattern.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.rollupindex.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.type.md create mode 100644 src/plugins/index_pattern_editor/README.md create mode 100644 src/plugins/index_pattern_editor/jest.config.js create mode 100644 src/plugins/index_pattern_editor/kibana.json create mode 100644 src/plugins/index_pattern_editor/public/components/_templates.scss create mode 100644 src/plugins/index_pattern_editor/public/components/_variables.scss create mode 100644 src/plugins/index_pattern_editor/public/components/advanced_params_content/advanced_params_content.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/advanced_params_content/advanced_params_section.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/advanced_params_content/index.ts rename src/plugins/{index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap => index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/__snapshots__/empty_index_list_prompt.test.tsx.snap} (71%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.scss => index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.scss} (86%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.test.tsx => index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.test.tsx} (69%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx => index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.tsx} (72%) create mode 100644 src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/index.ts rename src/plugins/{index_pattern_management/public/components/index_pattern_table => index_pattern_editor/public/components/empty_prompts}/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap (66%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table => index_pattern_editor/public/components/empty_prompts}/empty_index_pattern_prompt/assets/index_pattern_illustration.scss (100%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table => index_pattern_editor/public/components/empty_prompts}/empty_index_pattern_prompt/assets/index_pattern_illustration.tsx (99%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table => index_pattern_editor/public/components/empty_prompts}/empty_index_pattern_prompt/empty_index_pattern_prompt.scss (92%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table => index_pattern_editor/public/components/empty_prompts}/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx (82%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table => index_pattern_editor/public/components/empty_prompts}/empty_index_pattern_prompt/empty_index_pattern_prompt.tsx (65%) rename src/plugins/{index_pattern_management/public/components/index_pattern_table => index_pattern_editor/public/components/empty_prompts}/empty_index_pattern_prompt/index.tsx (100%) create mode 100644 src/plugins/index_pattern_editor/public/components/empty_prompts/empty_prompts.tsx rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/index.ts => index_pattern_editor/public/components/empty_prompts/index.tsx} (88%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/header => index_pattern_editor/public/components/empty_prompts/prompt_footer}/index.ts (88%) create mode 100644 src/plugins/index_pattern_editor/public/components/empty_prompts/prompt_footer/prompt_footer.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panel.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels.scss create mode 100644 src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels.tsx rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/loading_state.test.tsx => index_pattern_editor/public/components/flyout_panels/flyout_panels_content.tsx} (50%) create mode 100644 src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_footer.tsx rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.test.tsx => index_pattern_editor/public/components/flyout_panels/flyout_panels_header.tsx} (53%) create mode 100644 src/plugins/index_pattern_editor/public/components/flyout_panels/index.ts create mode 100644 src/plugins/index_pattern_editor/public/components/footer/footer.tsx rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header => index_pattern_editor/public/components/footer}/index.ts (91%) create mode 100644 src/plugins/index_pattern_editor/public/components/form_fields/index.ts create mode 100644 src/plugins/index_pattern_editor/public/components/form_fields/timestamp_field.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/form_fields/title_field.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/form_fields/type_field.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/form_schema.ts create mode 100644 src/plugins/index_pattern_editor/public/components/i18n_texts.ts create mode 100644 src/plugins/index_pattern_editor/public/components/index.ts create mode 100644 src/plugins/index_pattern_editor/public/components/index_pattern_editor.scss create mode 100644 src/plugins/index_pattern_editor/public/components/index_pattern_editor.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/index_pattern_editor_flyout_content.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/index_pattern_editor_lazy.tsx create mode 100644 src/plugins/index_pattern_editor/public/components/index_pattern_flyout_content_container.tsx rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern => index_pattern_editor/public}/components/loading_indices/__snapshots__/loading_indices.test.tsx.snap (88%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern => index_pattern_editor/public}/components/loading_indices/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern => index_pattern_editor/public}/components/loading_indices/loading_indices.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern => index_pattern_editor/public}/components/loading_indices/loading_indices.tsx (93%) rename src/plugins/{index_pattern_management/public/components/create_button => index_pattern_editor/public/components/preview_panel}/index.ts (88%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components => index_pattern_editor/public/components/preview_panel}/indices_list/__snapshots__/indices_list.test.tsx.snap (97%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components => index_pattern_editor/public/components/preview_panel}/indices_list/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components => index_pattern_editor/public/components/preview_panel}/indices_list/indices_list.test.tsx (97%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components => index_pattern_editor/public/components/preview_panel}/indices_list/indices_list.tsx (96%) create mode 100644 src/plugins/index_pattern_editor/public/components/preview_panel/preview_panel.tsx rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components => index_pattern_editor/public/components/preview_panel}/status_message/__snapshots__/status_message.test.tsx.snap (66%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components => index_pattern_editor/public/components/preview_panel}/status_message/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components => index_pattern_editor/public/components/preview_panel}/status_message/status_message.test.tsx (96%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components => index_pattern_editor/public/components/preview_panel}/status_message/status_message.tsx (58%) create mode 100644 src/plugins/index_pattern_editor/public/components/rollup_beta_warning/index.ts create mode 100644 src/plugins/index_pattern_editor/public/components/rollup_beta_warning/rollup_beta_warning.tsx create mode 100644 src/plugins/index_pattern_editor/public/constants.ts create mode 100644 src/plugins/index_pattern_editor/public/index.ts rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/__snapshots__/get_indices.test.ts.snap (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/can_append_wildcard.test.ts (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/can_append_wildcard.ts (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/contains_illegal_characters.ts (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/contains_invalid_characters.test.ts (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/ensure_minimum_time.test.ts (79%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/ensure_minimum_time.ts (95%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/extract_time_fields.test.ts (76%) create mode 100644 src/plugins/index_pattern_editor/public/lib/extract_time_fields.ts rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/get_indices.test.ts (79%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/get_indices.ts (73%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/get_matched_indices.test.ts (100%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/get_matched_indices.ts (97%) rename src/plugins/{index_pattern_management/public/components/create_index_pattern_wizard => index_pattern_editor/public}/lib/index.ts (100%) create mode 100644 src/plugins/index_pattern_editor/public/mocks.ts create mode 100644 src/plugins/index_pattern_editor/public/open_editor.tsx create mode 100644 src/plugins/index_pattern_editor/public/plugin.test.tsx create mode 100644 src/plugins/index_pattern_editor/public/plugin.tsx create mode 100644 src/plugins/index_pattern_editor/public/shared_imports.ts create mode 100644 src/plugins/index_pattern_editor/public/test_utils/helpers.ts create mode 100644 src/plugins/index_pattern_editor/public/test_utils/index.ts create mode 100644 src/plugins/index_pattern_editor/public/test_utils/mocks.ts create mode 100644 src/plugins/index_pattern_editor/public/test_utils/test_utils.ts create mode 100644 src/plugins/index_pattern_editor/public/types.ts create mode 100644 src/plugins/index_pattern_editor/tsconfig.json delete mode 100644 src/plugins/index_pattern_management/public/components/create_button/create_button.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/CREATE_INDEX_PATTERN.md delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/__snapshots__/loading_state.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/loading_state.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/__snapshots__/step_index_pattern.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/__snapshots__/header.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/index.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/action_buttons/action_buttons.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/action_buttons/index.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/__snapshots__/advanced_options.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/advanced_options.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/advanced_options.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/index.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/__snapshots__/header.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/index.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/__snapshots__/time_field.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/index.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/index.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/constants/index.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/index.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.ts delete mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts delete mode 100644 src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/index.ts delete mode 100644 src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts delete mode 100644 src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx delete mode 100644 src/plugins/index_pattern_management/public/service/creation/config.ts delete mode 100644 src/plugins/index_pattern_management/public/service/creation/index.ts delete mode 100644 src/plugins/index_pattern_management/public/service/creation/manager.ts delete mode 100644 src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js delete mode 100644 src/plugins/index_pattern_management/public/service/index.ts delete mode 100644 src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts delete mode 100644 src/plugins/index_pattern_management/public/service/list/config.ts delete mode 100644 src/plugins/index_pattern_management/public/service/list/index.ts delete mode 100644 src/plugins/index_pattern_management/public/service/list/manager.ts delete mode 100644 src/plugins/index_pattern_management/public/service/list/rollup_list_config.ts diff --git a/.i18nrc.json b/.i18nrc.json index 269b02ecee87a..235b65d7502f4 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -36,6 +36,7 @@ "monaco": "packages/kbn-monaco/src", "esQuery": "packages/kbn-es-query/src", "presentationUtil": "src/plugins/presentation_util", + "indexPatternEditor": "src/plugins/index_pattern_editor", "indexPatternFieldEditor": "src/plugins/index_pattern_field_editor", "indexPatternManagement": "src/plugins/index_pattern_management", "interactiveSetup": "src/plugins/interactive_setup", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 5c47d0f46bbfe..dc410f2e5f2a5 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -123,6 +123,10 @@ for use in their own application. |Moves the legacy ui/registry/feature_catalogue module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls. +|{kib-repo}blob/{branch}/src/plugins/index_pattern_editor/README.md[indexPatternEditor] +|Create index patterns from within Kibana apps. + + |{kib-repo}blob/{branch}/src/plugins/index_pattern_field_editor/README.md[indexPatternFieldEditor] |The reusable field editor across Kibana! diff --git a/docs/development/core/public/kibana-plugin-core-public.overlayflyoutopenoptions.hideclosebutton.md b/docs/development/core/public/kibana-plugin-core-public.overlayflyoutopenoptions.hideclosebutton.md new file mode 100644 index 0000000000000..149a53f35d34d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.overlayflyoutopenoptions.hideclosebutton.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [OverlayFlyoutOpenOptions](./kibana-plugin-core-public.overlayflyoutopenoptions.md) > [hideCloseButton](./kibana-plugin-core-public.overlayflyoutopenoptions.hideclosebutton.md) + +## OverlayFlyoutOpenOptions.hideCloseButton property + +Signature: + +```typescript +hideCloseButton?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.overlayflyoutopenoptions.md b/docs/development/core/public/kibana-plugin-core-public.overlayflyoutopenoptions.md index 6665ebde295bc..fc4959b87a987 100644 --- a/docs/development/core/public/kibana-plugin-core-public.overlayflyoutopenoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.overlayflyoutopenoptions.md @@ -18,6 +18,7 @@ export interface OverlayFlyoutOpenOptions | ["data-test-subj"](./kibana-plugin-core-public.overlayflyoutopenoptions._data-test-subj_.md) | string | | | [className](./kibana-plugin-core-public.overlayflyoutopenoptions.classname.md) | string | | | [closeButtonAriaLabel](./kibana-plugin-core-public.overlayflyoutopenoptions.closebuttonarialabel.md) | string | | +| [hideCloseButton](./kibana-plugin-core-public.overlayflyoutopenoptions.hideclosebutton.md) | boolean | | | [maxWidth](./kibana-plugin-core-public.overlayflyoutopenoptions.maxwidth.md) | boolean | number | string | | | [ownFocus](./kibana-plugin-core-public.overlayflyoutopenoptions.ownfocus.md) | boolean | | | [size](./kibana-plugin-core-public.overlayflyoutopenoptions.size.md) | EuiFlyoutSize | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternaggrestrictions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggregationrestrictions.md similarity index 69% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternaggrestrictions.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggregationrestrictions.md index 324bd1e410c6c..b3d04027980ca 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternaggrestrictions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggregationrestrictions.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternAggRestrictions](./kibana-plugin-plugins-data-public.indexpatternaggrestrictions.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggregationRestrictions](./kibana-plugin-plugins-data-public.aggregationrestrictions.md) -## IndexPatternAggRestrictions type +## AggregationRestrictions type Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.allownoindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.allownoindex.md new file mode 100644 index 0000000000000..091fb4d8aaa44 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.allownoindex.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [GetFieldsOptions](./kibana-plugin-plugins-data-public.getfieldsoptions.md) > [allowNoIndex](./kibana-plugin-plugins-data-public.getfieldsoptions.allownoindex.md) + +## GetFieldsOptions.allowNoIndex property + +Signature: + +```typescript +allowNoIndex?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.lookback.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.lookback.md new file mode 100644 index 0000000000000..0e8c7e34b1fe8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.lookback.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [GetFieldsOptions](./kibana-plugin-plugins-data-public.getfieldsoptions.md) > [lookBack](./kibana-plugin-plugins-data-public.getfieldsoptions.lookback.md) + +## GetFieldsOptions.lookBack property + +Signature: + +```typescript +lookBack?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.md new file mode 100644 index 0000000000000..056018174baf6 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [GetFieldsOptions](./kibana-plugin-plugins-data-public.getfieldsoptions.md) + +## GetFieldsOptions interface + +Signature: + +```typescript +export interface GetFieldsOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-public.getfieldsoptions.allownoindex.md) | boolean | | +| [lookBack](./kibana-plugin-plugins-data-public.getfieldsoptions.lookback.md) | boolean | | +| [metaFields](./kibana-plugin-plugins-data-public.getfieldsoptions.metafields.md) | string[] | | +| [pattern](./kibana-plugin-plugins-data-public.getfieldsoptions.pattern.md) | string | | +| [rollupIndex](./kibana-plugin-plugins-data-public.getfieldsoptions.rollupindex.md) | string | | +| [type](./kibana-plugin-plugins-data-public.getfieldsoptions.type.md) | string | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.metafields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.metafields.md new file mode 100644 index 0000000000000..87c0f9d9bfe5b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.metafields.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [GetFieldsOptions](./kibana-plugin-plugins-data-public.getfieldsoptions.md) > [metaFields](./kibana-plugin-plugins-data-public.getfieldsoptions.metafields.md) + +## GetFieldsOptions.metaFields property + +Signature: + +```typescript +metaFields?: string[]; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.pattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.pattern.md new file mode 100644 index 0000000000000..c6c53b2cf7bc8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.pattern.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [GetFieldsOptions](./kibana-plugin-plugins-data-public.getfieldsoptions.md) > [pattern](./kibana-plugin-plugins-data-public.getfieldsoptions.pattern.md) + +## GetFieldsOptions.pattern property + +Signature: + +```typescript +pattern: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.rollupindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.rollupindex.md new file mode 100644 index 0000000000000..4711e3bdfce92 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.rollupindex.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [GetFieldsOptions](./kibana-plugin-plugins-data-public.getfieldsoptions.md) > [rollupIndex](./kibana-plugin-plugins-data-public.getfieldsoptions.rollupindex.md) + +## GetFieldsOptions.rollupIndex property + +Signature: + +```typescript +rollupIndex?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.type.md new file mode 100644 index 0000000000000..cdc4c562b5611 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getfieldsoptions.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [GetFieldsOptions](./kibana-plugin-plugins-data-public.getfieldsoptions.md) > [type](./kibana-plugin-plugins-data-public.getfieldsoptions.type.md) + +## GetFieldsOptions.type property + +Signature: + +```typescript +type?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index c13f8282242ff..3716f5cb4febf 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -63,6 +63,7 @@ | [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) | Data plugin public Start contract | | [DataPublicPluginStartActions](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.md) | utilities to generate filters from action context | | [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) | Data plugin prewired UI components | +| [GetFieldsOptions](./kibana-plugin-plugins-data-public.getfieldsoptions.md) | | | [IDataPluginServices](./kibana-plugin-plugins-data-public.idatapluginservices.md) | | | [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) | | | [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) | | @@ -141,6 +142,7 @@ | [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) | | | [AggGroupName](./kibana-plugin-plugins-data-public.agggroupname.md) | | | [AggParam](./kibana-plugin-plugins-data-public.aggparam.md) | | +| [AggregationRestrictions](./kibana-plugin-plugins-data-public.aggregationrestrictions.md) | | | [AggsStart](./kibana-plugin-plugins-data-public.aggsstart.md) | AggsStart represents the actual external contract as AggsCommonStart is only used internally. The difference is that AggsStart includes the typings for the registry with initialized agg types. | | [AutocompleteStart](./kibana-plugin-plugins-data-public.autocompletestart.md) | \* | | [AutoRefreshDoneFn](./kibana-plugin-plugins-data-public.autorefreshdonefn.md) | | @@ -163,7 +165,6 @@ | [IFieldParamType](./kibana-plugin-plugins-data-public.ifieldparamtype.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) | | | [IMetricAggType](./kibana-plugin-plugins-data-public.imetricaggtype.md) | | -| [IndexPatternAggRestrictions](./kibana-plugin-plugins-data-public.indexpatternaggrestrictions.md) | | | [IndexPatternLoadExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.indexpatternloadexpressionfunctiondefinition.md) | | | [IndexPatternsContract](./kibana-plugin-plugins-data-public.indexpatternscontract.md) | | | [IndexPatternSelectProps](./kibana-plugin-plugins-data-public.indexpatternselectprops.md) | | diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 67eaff4a4554c..10ce4f7ab1d1f 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -34,6 +34,7 @@ pageLoadAssetSize: indexLifecycleManagement: 107090 indexManagement: 140608 indexPatternManagement: 28222 + indexPatternEditor: 25000 infra: 184320 fleet: 465774 ingestPipelines: 58003 @@ -119,4 +120,3 @@ pageLoadAssetSize: expressionMetric: 22238 expressionShape: 34008 interactiveSetup: 18532 - \ No newline at end of file diff --git a/src/core/public/overlays/flyout/flyout_service.tsx b/src/core/public/overlays/flyout/flyout_service.tsx index 603736f08268f..b41b85e5f429f 100644 --- a/src/core/public/overlays/flyout/flyout_service.tsx +++ b/src/core/public/overlays/flyout/flyout_service.tsx @@ -84,6 +84,7 @@ export interface OverlayFlyoutOpenOptions { 'data-test-subj'?: string; size?: EuiFlyoutSize; maxWidth?: boolean | number | string; + hideCloseButton?: boolean; } interface StartDeps { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 8e0087cbeeed0..80fd3927e05a3 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1027,6 +1027,8 @@ export interface OverlayFlyoutOpenOptions { // (undocumented) closeButtonAriaLabel?: string; // (undocumented) + hideCloseButton?: boolean; + // (undocumented) maxWidth?: boolean | number | string; // (undocumented) ownFocus?: boolean; diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 0f2e4afbdcd28..64628f7165f27 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -528,7 +528,7 @@ export class IndexPatternsService { const indexPattern = await this.create(spec, skipFetchFields); const createdIndexPattern = await this.createSavedObject(indexPattern, override); await this.setDefault(createdIndexPattern.id!); - return createdIndexPattern; + return createdIndexPattern!; } /** diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 803b93cc7acf5..fb35d00545040 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -83,7 +83,9 @@ export { IndexPatternSpec, IndexPatternLoadExpressionFunctionDefinition, fieldList, + GetFieldsOptions, INDEX_PATTERN_SAVED_OBJECT_TYPE, + AggregationRestrictions, IndexPatternType, } from '../common'; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 7e67fdbe667ea..64b73ded88eef 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -538,6 +538,22 @@ export class AggParamType extends Ba makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig; } +// Warning: (ae-missing-release-tag) "AggregationRestrictions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +type AggregationRestrictions = Record; + +export { AggregationRestrictions } + +export { AggregationRestrictions as IndexPatternAggRestrictions } + // Warning: (ae-forgotten-export) The symbol "AggsCommonStart" needs to be exported by the entry point index.d.ts // // @public @@ -987,6 +1003,24 @@ export function getEsPreference(uiSettings: IUiSettingsClient_2, sessionId?: str // @public (undocumented) export function getEsQueryConfig(config: KibanaConfig): EsQueryConfig_2; +// Warning: (ae-missing-release-tag) "GetFieldsOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface GetFieldsOptions { + // (undocumented) + allowNoIndex?: boolean; + // (undocumented) + lookBack?: boolean; + // (undocumented) + metaFields?: string[]; + // (undocumented) + pattern: string; + // (undocumented) + rollupIndex?: string; + // (undocumented) + type?: string; +} + // Warning: (ae-missing-release-tag) "getKbnTypeNames" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @deprecated (undocumented) @@ -1303,18 +1337,6 @@ export class IndexPattern implements IIndexPattern { version: string | undefined; } -// Warning: (ae-missing-release-tag) "AggregationRestrictions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type IndexPatternAggRestrictions = Record; - // Warning: (ae-missing-release-tag) "IndexPatternAttributes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -1506,7 +1528,6 @@ export class IndexPatternsService { getDefault: () => Promise; getDefaultId: () => Promise; getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; - // Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts getFieldsForWildcard: (options: GetFieldsOptions) => Promise; getIds: (refresh?: boolean) => Promise; getIdsWithTitle: (refresh?: boolean) => Promise; + aggs?: Record; // (undocumented) params?: { rollup_index: string; @@ -2483,20 +2504,20 @@ export interface WaitUntilNextSessionCompletesOptions { // src/plugins/data/public/index.ts:54:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:54:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:54:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:225:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:225:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:225:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:227:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:228:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:237:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:238:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:239:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:240:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:244:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:245:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:248:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:249:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:252:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:227:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:227:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:227:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:229:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:230:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:239:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:240:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:241:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:242:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:246:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:247:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:250:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:251:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:254:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:62:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/index_pattern_editor/README.md b/src/plugins/index_pattern_editor/README.md new file mode 100644 index 0000000000000..3ffb589230732 --- /dev/null +++ b/src/plugins/index_pattern_editor/README.md @@ -0,0 +1,48 @@ +# Index pattern editor + +Create index patterns from within Kibana apps. + +## How to use + +You first need to add in your kibana.json the "`indexPatternEditor`" plugin as a required dependency of your plugin. + +You will then receive in the start contract of the indexPatternEditor plugin the following API: + +### `userPermissions.editIndexPattern(): boolean` + +Convenience method that uses the `core.application.capabilities` api to determine whether the user can create or edit the index pattern. + +### `openEditor(options: IndexPatternEditorProps): CloseEditor` + +Use this method to display the index pattern editor to create an index pattern. + +#### `options` + +`onSave: (indexPattern: IndexPattern) => void` (**required**) + +You must provide an `onSave` handler to be notified when an index pattern has been created/updated. This handler is called after the index pattern has been persisted as a saved object. + +`onCancel: () => void;` (optional) + +You can optionally pass an `onCancel` handler which is called when the index pattern creation flyout is closed wihtout creating an index pattern. + +`defaultTypeIsRollup: boolean` (optional, default false) + +The default index pattern type can be optionally specified as `rollup`. + +`requireTimestampField: boolean` (optional, default false) + +The editor can require a timestamp field on the index pattern. + +### IndexPatternEditorComponent + +This the React component interface equivalent to `openEditor`. It takes the same arguments - + +```tsx + +``` diff --git a/src/plugins/index_pattern_editor/jest.config.js b/src/plugins/index_pattern_editor/jest.config.js new file mode 100644 index 0000000000000..0a018a42d06e6 --- /dev/null +++ b/src/plugins/index_pattern_editor/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/index_pattern_editor'], +}; diff --git a/src/plugins/index_pattern_editor/kibana.json b/src/plugins/index_pattern_editor/kibana.json new file mode 100644 index 0000000000000..b4a1bea555616 --- /dev/null +++ b/src/plugins/index_pattern_editor/kibana.json @@ -0,0 +1,13 @@ +{ + "id": "indexPatternEditor", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["data"], + "requiredBundles": ["kibanaReact", "esUiShared"], + "owner": { + "name": "App Services", + "githubTeam": "kibana-app-services" + }, + "description": "This plugin provides the ability to create index patterns via a modal flyout from any kibana app" +} diff --git a/src/plugins/index_pattern_editor/public/components/_templates.scss b/src/plugins/index_pattern_editor/public/components/_templates.scss new file mode 100644 index 0000000000000..5303537bddabc --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/_templates.scss @@ -0,0 +1,11 @@ +%inp-empty-state-footer { + background: $euiColorLightestShade; + margin: 0 (-$euiSizeL) (-$euiSizeL); + padding: $euiSizeL; + border-radius: 0 0 $euiBorderRadius $euiBorderRadius; + + // sass-lint:disable-block mixins-before-declarations + @include euiBreakpoint('xs', 's') { + text-align: center; + } +} diff --git a/src/plugins/index_pattern_editor/public/components/_variables.scss b/src/plugins/index_pattern_editor/public/components/_variables.scss new file mode 100644 index 0000000000000..5da25a91bd77c --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/_variables.scss @@ -0,0 +1 @@ +$inpEmptyStateMaxWidth: $euiSizeXXL * 19; diff --git a/src/plugins/index_pattern_editor/public/components/advanced_params_content/advanced_params_content.tsx b/src/plugins/index_pattern_editor/public/components/advanced_params_content/advanced_params_content.tsx new file mode 100644 index 0000000000000..ad876fed51801 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/advanced_params_content/advanced_params_content.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +import { UseField, TextField, ToggleField } from '../../shared_imports'; +import { IndexPatternConfig } from '../../types'; + +import { AdvancedParamsSection } from './advanced_params_section'; + +const allowHiddenAriaLabel = i18n.translate('indexPatternEditor.form.allowHiddenAriaLabel', { + defaultMessage: 'Allow hidden and system indices', +}); + +const customIndexPatternIdLabel = i18n.translate( + 'indexPatternEditor.form.customIndexPatternIdLabel', + { + defaultMessage: 'Custom index pattern ID', + } +); + +interface AdvancedParamsContentProps { + disableAllowHidden: boolean; +} + +export const AdvancedParamsContent = ({ disableAllowHidden }: AdvancedParamsContentProps) => ( + + + + + path={'allowHidden'} + component={ToggleField} + data-test-subj="allowHiddenField" + componentProps={{ + euiFieldProps: { + 'aria-label': allowHiddenAriaLabel, + disabled: disableAllowHidden, + }, + }} + /> + + + + + + + path={'id'} + component={TextField} + data-test-subj="savedObjectIdField" + componentProps={{ + euiFieldProps: { + 'aria-label': customIndexPatternIdLabel, + }, + }} + /> + + + +); diff --git a/src/plugins/index_pattern_editor/public/components/advanced_params_content/advanced_params_section.tsx b/src/plugins/index_pattern_editor/public/components/advanced_params_content/advanced_params_section.tsx new file mode 100644 index 0000000000000..b313fddb8ee2d --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/advanced_params_content/advanced_params_section.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; + +interface Props { + children: React.ReactNode; +} + +export const AdvancedParamsSection = ({ children }: Props) => { + const [isVisible, setIsVisible] = useState(false); + + const toggleIsVisible = useCallback(() => { + setIsVisible(!isVisible); + }, [isVisible]); + + return ( + <> + + {isVisible + ? i18n.translate('indexPatternEditor.editor.form.advancedSettings.hideButtonLabel', { + defaultMessage: 'Hide advanced settings', + }) + : i18n.translate('indexPatternEditor.editor.form.advancedSettings.showButtonLabel', { + defaultMessage: 'Show advanced settings', + })} + + +
+ + {/* We ned to wrap the children inside a "div" to have our css :first-child rule */} +
{children}
+
+ + ); +}; diff --git a/src/plugins/index_pattern_editor/public/components/advanced_params_content/index.ts b/src/plugins/index_pattern_editor/public/components/advanced_params_content/index.ts new file mode 100644 index 0000000000000..a285004d1f45d --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/advanced_params_content/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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { AdvancedParamsContent } from './advanced_params_content'; diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/__snapshots__/empty_index_list_prompt.test.tsx.snap similarity index 71% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/__snapshots__/empty_index_list_prompt.test.tsx.snap index 75b8177d9dac3..a104c36e3a8a0 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/__snapshots__/empty_index_list_prompt.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EmptyState should render normally 1`] = ` +exports[`EmptyIndexListPrompt should render normally 1`] = ` @@ -38,7 +38,7 @@ exports[`EmptyState should render normally 1`] = ` description={ } @@ -53,7 +53,7 @@ exports[`EmptyState should render normally 1`] = ` title={ } @@ -65,7 +65,7 @@ exports[`EmptyState should render normally 1`] = ` description={ } @@ -80,7 +80,7 @@ exports[`EmptyState should render normally 1`] = ` title={ } @@ -92,7 +92,7 @@ exports[`EmptyState should render normally 1`] = ` description={ } @@ -107,7 +107,7 @@ exports[`EmptyState should render normally 1`] = ` title={ } @@ -131,18 +131,18 @@ exports[`EmptyState should render normally 1`] = ` Object { "description": , "title": , }, @@ -164,7 +164,7 @@ exports[`EmptyState should render normally 1`] = ` > @@ -175,7 +175,7 @@ exports[`EmptyState should render normally 1`] = ` , "title": , }, @@ -184,33 +184,33 @@ exports[`EmptyState should render normally 1`] = ` /> + + + + + , + } + } + /> + - - - - - , - } - } - /> - `; diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.scss b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.scss similarity index 86% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.scss rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.scss index 37889b9d7c483..845ddfb3eb234 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.scss +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.scss @@ -1,5 +1,5 @@ -@import '../../../variables'; -@import '../../../templates'; +@import '../../variables'; +@import '../../templates'; .inpEmptyState { // override EUI specificity diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.test.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.test.tsx similarity index 69% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.test.tsx rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.test.tsx index ed68d4d4ddd64..f5a996a441515 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.test.tsx +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.test.tsx @@ -7,14 +7,11 @@ */ import React from 'react'; -import { EmptyState } from '../empty_state'; +import { EmptyIndexListPrompt } from './empty_index_list_prompt'; import { shallow } from 'enzyme'; import sinon from 'sinon'; import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl } from '@kbn/test/jest'; -import { docLinksServiceMock } from '../../../../../../core/public/mocks'; - -const docLinks = docLinksServiceMock.createStartContract(); jest.mock('react-router-dom', () => ({ useHistory: () => ({ @@ -22,14 +19,16 @@ jest.mock('react-router-dom', () => ({ }), })); -describe('EmptyState', () => { +describe('EmptyIndexListPrompt', () => { it('should render normally', () => { const component = shallow( - {}} - navigateToApp={async () => {}} - canSave={true} + createAnyway={() => {}} + closeFlyout={() => {}} + addDataUrl={'http://elastic.co'} + navigateToApp={async (appId) => {}} + canSaveIndexPattern={true} /> ); @@ -42,11 +41,13 @@ describe('EmptyState', () => { const onRefreshHandler = sinon.stub(); const component = mountWithIntl( - {}} - canSave={true} + createAnyway={() => {}} + closeFlyout={() => {}} + addDataUrl={'http://elastic.co'} + navigateToApp={async (appId) => {}} + canSaveIndexPattern={true} /> ); diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.tsx similarity index 72% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.tsx index af49e8c36fe3b..1331eb9b7c4ac 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.tsx @@ -6,10 +6,9 @@ * Side Public License, v 1. */ -import './empty_state.scss'; +import './empty_index_list_prompt.scss'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { DocLinksStart, ApplicationStart } from 'kibana/public'; import { EuiPageContentHeader, EuiPageContentHeaderSection, @@ -26,30 +25,34 @@ import { EuiText, EuiFlexGroup, } from '@elastic/eui'; -import { useHistory } from 'react-router-dom'; -import { reactRouterNavigate } from '../../../../../../plugins/kibana_react/public'; -export const EmptyState = ({ +import { ApplicationStart } from 'src/core/public'; + +export const EmptyIndexListPrompt = ({ onRefresh, + closeFlyout, + createAnyway, + canSaveIndexPattern, + addDataUrl, navigateToApp, - docLinks, - canSave, }: { onRefresh: () => void; + closeFlyout: () => void; + createAnyway: () => void; + canSaveIndexPattern: boolean; + addDataUrl: string; navigateToApp: ApplicationStart['navigateToApp']; - docLinks: DocLinksStart; - canSave: boolean; }) => { - const createAnyway = ( + const createAnywayLink = ( + createAnyway()} data-test-subj="createAnyway"> @@ -74,7 +77,7 @@ export const EmptyState = ({

@@ -87,17 +90,20 @@ export const EmptyState = ({ navigateToApp('home', { path: '#/tutorial_directory' })} + onClick={() => { + navigateToApp('home', { path: '#/tutorial_directory' }); + closeFlyout(); + }} icon={} title={ } description={ } @@ -110,13 +116,13 @@ export const EmptyState = ({ icon={} title={ } description={ } @@ -125,17 +131,20 @@ export const EmptyState = ({ navigateToApp('home', { path: '#/tutorial_directory/sampleData' })} + onClick={() => { + navigateToApp('home', { path: '#/tutorial_directory/sampleData' }); + closeFlyout(); + }} icon={} title={ } description={ } @@ -151,14 +160,14 @@ export const EmptyState = ({ { title: ( ), description: ( - + @@ -173,14 +182,14 @@ export const EmptyState = ({ { title: ( ), description: ( {' '} @@ -191,11 +200,11 @@ export const EmptyState = ({ /> + + {canSaveIndexPattern && createAnywayLink} - - {canSave && createAnyway} ); }; diff --git a/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/index.ts b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/index.ts new file mode 100644 index 0000000000000..ae5dcecf069c7 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { EmptyIndexListPrompt } from './empty_index_list_prompt'; diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap similarity index 66% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap index bc69fa29e6904..6216f04c0a1a9 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap @@ -19,7 +19,15 @@ exports[`EmptyIndexPatternPrompt should render normally 1`] = ` className="inpEmptyIndexPatternPrompt__illustration" grow={1} > - + + } + > + +

- - +
@@ -79,19 +83,19 @@ exports[`EmptyIndexPatternPrompt should render normally 1`] = ` > diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/assets/index_pattern_illustration.scss b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/assets/index_pattern_illustration.scss similarity index 100% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/assets/index_pattern_illustration.scss rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/assets/index_pattern_illustration.scss diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/assets/index_pattern_illustration.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/assets/index_pattern_illustration.tsx similarity index 99% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/assets/index_pattern_illustration.tsx rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/assets/index_pattern_illustration.tsx index 3666c1be7b5d2..09b18e25e9d00 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/assets/index_pattern_illustration.tsx +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/assets/index_pattern_illustration.tsx @@ -537,4 +537,5 @@ const IndexPatternIllustration = () => ( ); -export const Illustration = IndexPatternIllustration; +/* eslint-disable import/no-default-export */ +export default IndexPatternIllustration; diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.scss b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.scss similarity index 92% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.scss rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.scss index 11ac55b098a57..f6db2fc89f353 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.scss +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.scss @@ -1,5 +1,5 @@ -@import '../../../variables'; -@import '../../../templates'; +@import '../../variables'; +@import '../../templates'; .inpEmptyIndexPatternPrompt { // override EUI specificity diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx similarity index 82% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx index 1cd0574a35d15..4cf3c4b55acde 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx @@ -14,10 +14,9 @@ describe('EmptyIndexPatternPrompt', () => { it('should render normally', () => { const component = shallowWithI18nProvider( {} }]} - docLinksIndexPatternIntro={'testUrl'} - setBreadcrumbs={() => {}} + goToCreate={() => {}} + canSaveIndexPattern={true} + indexPatternsIntroUrl={'http://elastic.co/'} /> ); diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.tsx similarity index 65% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.tsx rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.tsx index f36f3a38c7380..054ff9d13b043 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/empty_index_pattern_prompt.tsx +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.tsx @@ -8,34 +8,26 @@ import './empty_index_pattern_prompt.scss'; -import React from 'react'; +import React, { lazy, Suspense } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageContent, EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { EuiDescriptionListTitle } from '@elastic/eui'; import { EuiDescriptionListDescription, EuiDescriptionList } from '@elastic/eui'; -import { EuiLink } from '@elastic/eui'; -import { getListBreadcrumbs } from '../../breadcrumbs'; -import { IndexPatternCreationOption } from '../../types'; -import { CreateButton } from '../../create_button'; -import { Illustration } from './assets/index_pattern_illustration'; -import { ManagementAppMountParams } from '../../../../../management/public'; - +import { EuiLink, EuiButton, EuiLoadingSpinner } from '@elastic/eui'; interface Props { - canSave: boolean; - creationOptions: IndexPatternCreationOption[]; - docLinksIndexPatternIntro: string; - setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; + goToCreate: () => void; + canSaveIndexPattern: boolean; + indexPatternsIntroUrl: string; } +const Illustration = lazy(() => import('./assets/index_pattern_illustration')); + export const EmptyIndexPatternPrompt = ({ - canSave, - creationOptions, - docLinksIndexPatternIntro, - setBreadcrumbs, + goToCreate, + canSaveIndexPattern, + indexPatternsIntroUrl, }: Props) => { - setBreadcrumbs(getListBreadcrumbs()); - return ( - + }> + +


- {canSave && ( - + {canSaveIndexPattern && ( + - + )}
@@ -85,14 +84,14 @@ export const EmptyIndexPatternPrompt = ({ - + diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/index.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/index.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/index.tsx rename to src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_pattern_prompt/index.tsx diff --git a/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_prompts.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_prompts.tsx new file mode 100644 index 0000000000000..3b06fa1cff298 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_prompts.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useCallback, FC } from 'react'; + +import { useKibana } from '../../shared_imports'; + +import { MatchedItem, ResolveIndexResponseItemAlias, IndexPatternEditorContext } from '../../types'; + +import { getIndices } from '../../lib'; + +import { EmptyIndexListPrompt } from './empty_index_list_prompt'; +import { EmptyIndexPatternPrompt } from './empty_index_pattern_prompt'; +import { PromptFooter } from './prompt_footer'; + +const removeAliases = (item: MatchedItem) => + !((item as unknown) as ResolveIndexResponseItemAlias).indices; + +interface Props { + onCancel: () => void; + allSources: MatchedItem[]; + hasExistingIndexPatterns: boolean; + loadSources: () => void; +} + +export const EmptyPrompts: FC = ({ + hasExistingIndexPatterns, + allSources, + onCancel, + children, + loadSources, +}) => { + const { + services: { docLinks, application, http }, + } = useKibana(); + + const [remoteClustersExist, setRemoteClustersExist] = useState(false); + const [goToForm, setGoToForm] = useState(false); + + const hasDataIndices = allSources.some(({ name }: MatchedItem) => !name.startsWith('.')); + + useCallback(() => { + let isMounted = true; + if (!hasDataIndices) + getIndices(http, () => false, '*:*', false).then((dataSources) => { + if (isMounted) { + setRemoteClustersExist(!!dataSources.filter(removeAliases).length); + } + }); + return () => { + isMounted = false; + }; + }, [http, hasDataIndices]); + + if (!hasExistingIndexPatterns && !goToForm) { + if (!hasDataIndices && !remoteClustersExist) { + // load data + return ( + <> + setGoToForm(true)} + canSaveIndexPattern={application.capabilities.indexPatterns.save as boolean} + navigateToApp={application.navigateToApp} + addDataUrl={docLinks.links.indexPatterns.introduction} + /> + + + ); + } else { + // first time + return ( + <> + setGoToForm(true)} + indexPatternsIntroUrl={docLinks.links.indexPatterns.introduction} + canSaveIndexPattern={application.capabilities.indexPatterns.save as boolean} + /> + + + ); + } + } + + return <>{children}; +}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/index.ts b/src/plugins/index_pattern_editor/public/components/empty_prompts/index.tsx similarity index 88% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/index.ts rename to src/plugins/index_pattern_editor/public/components/empty_prompts/index.tsx index 6d39f5bacbcaa..20bf754e3771a 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/index.ts +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/index.tsx @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { LoadingState } from './loading_state'; +export { EmptyPrompts } from './empty_prompts'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/index.ts b/src/plugins/index_pattern_editor/public/components/empty_prompts/prompt_footer/index.ts similarity index 88% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/index.ts rename to src/plugins/index_pattern_editor/public/components/empty_prompts/prompt_footer/index.ts index 8f737b3a42613..52b2a82651b28 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/index.ts +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/prompt_footer/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { Header } from './header'; +export { PromptFooter } from './prompt_footer'; diff --git a/src/plugins/index_pattern_editor/public/components/empty_prompts/prompt_footer/prompt_footer.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/prompt_footer/prompt_footer.tsx new file mode 100644 index 0000000000000..d1ab3febc5ed3 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/prompt_footer/prompt_footer.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiFlyoutFooter, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; + +const closeButtonLabel = i18n.translate( + 'indexPatternEditor.editor.emptyPrompt.flyoutCloseButtonLabel', + { + defaultMessage: 'Close', + } +); + +interface PromptFooterProps { + onCancel: () => void; +} + +export const PromptFooter = ({ onCancel }: PromptFooterProps) => { + return ( + + + + + {closeButtonLabel} + + + + + ); +}; diff --git a/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panel.tsx b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panel.tsx new file mode 100644 index 0000000000000..8f9193a47327f --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panel.tsx @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { + CSSProperties, + useState, + useLayoutEffect, + useCallback, + createContext, + useContext, +} from 'react'; +import classnames from 'classnames'; +import { EuiFlexItem } from '@elastic/eui'; + +import { useFlyoutPanelsContext } from './flyout_panels'; + +interface Context { + registerFooter: () => void; + registerContent: () => void; +} + +const flyoutPanelContext = createContext({ + registerFooter: () => {}, + registerContent: () => {}, +}); + +export interface Props { + /** Width of the panel (in percent %) */ + width?: number; + /** EUI sass background */ + backgroundColor?: 'euiPageBackground' | 'euiEmptyShade'; + /** Add a border to the panel */ + border?: 'left' | 'right'; +} + +export const Panel: React.FC> = ({ + children, + width, + className = '', + backgroundColor, + border, + ...rest +}) => { + const [config, setConfig] = useState<{ hasFooter: boolean; hasContent: boolean }>({ + hasContent: false, + hasFooter: false, + }); + + /* eslint-disable @typescript-eslint/naming-convention */ + const classes = classnames('fieldEditor__flyoutPanel', className, { + 'fieldEditor__flyoutPanel--pageBackground': backgroundColor === 'euiPageBackground', + 'fieldEditor__flyoutPanel--emptyShade': backgroundColor === 'euiEmptyShade', + 'fieldEditor__flyoutPanel--leftBorder': border === 'left', + 'fieldEditor__flyoutPanel--rightBorder': border === 'right', + 'fieldEditor__flyoutPanel--withContent': config.hasContent, + }); + /* eslint-enable @typescript-eslint/naming-convention */ + + const { addPanel } = useFlyoutPanelsContext(); + + const registerContent = useCallback(() => { + setConfig((prev) => { + return { + ...prev, + hasContent: true, + }; + }); + }, []); + + const registerFooter = useCallback(() => { + setConfig((prev) => { + if (!prev.hasContent) { + throw new Error( + 'You need to add a when you add a ' + ); + } + return { + ...prev, + hasFooter: true, + }; + }); + }, []); + + useLayoutEffect(() => { + const removePanel = addPanel({ width }); + + return removePanel; + }, [width, addPanel]); + + const styles: CSSProperties = {}; + + if (width) { + styles.flexBasis = `${width}%`; + } + + return ( + + +
+ {children} +
+
+
+ ); +}; + +export const useFlyoutPanelContext = (): Context => { + const ctx = useContext(flyoutPanelContext); + + if (ctx === undefined) { + throw new Error('useFlyoutPanel() must be used within a '); + } + + return ctx; +}; diff --git a/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels.scss b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels.scss new file mode 100644 index 0000000000000..8ba2770003954 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels.scss @@ -0,0 +1,42 @@ +.fieldEditor__flyoutPanels { + height: 100%; +} + +.fieldEditor__flyoutPanel { + height: 100%; + overflow-y: auto; + padding: $euiSizeL $euiSizeL 0 $euiSizeL; + + &--pageBackground { + background-color: $euiPageBackgroundColor; + } + &--emptyShade { + background-color: $euiColorEmptyShade; + } + &--leftBorder { + border-left: $euiBorderThin; + } + &--rightBorder { + border-right: $euiBorderThin; + } + &--withContent { + padding: 0; + overflow-y: hidden; + display: flex; + flex-direction: column; + } + + &__header { + padding: 0 !important; + } + + &__content { + flex: 1; + overflow-y: auto; + padding: $euiSizeL; + } + + &__footer { + flex: 0; + } +} diff --git a/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels.tsx b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels.tsx new file mode 100644 index 0000000000000..c878090ec7c1b --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { + useState, + createContext, + useContext, + useCallback, + useMemo, + useLayoutEffect, +} from 'react'; +import { EuiFlexGroup, EuiFlexGroupProps } from '@elastic/eui'; + +import './flyout_panels.scss'; + +interface Panel { + width?: number; +} + +interface Context { + addPanel: (panel: Panel) => () => void; +} + +let idx = 0; + +const panelId = () => idx++; + +const flyoutPanelsContext = createContext({ + addPanel() { + return () => {}; + }, +}); + +export interface Props { + /** + * The total max width with all the panels in the DOM + * Corresponds to the "maxWidth" prop passed to the EuiFlyout + */ + maxWidth: number; + /** The className selector of the flyout */ + flyoutClassName: string; + /** The size between the panels. Corresponds to EuiFlexGroup gutterSize */ + gutterSize?: EuiFlexGroupProps['gutterSize']; +} + +export const Panels: React.FC = ({ maxWidth, flyoutClassName, ...props }) => { + const flyoutDOMelement = useMemo(() => { + const el = document.getElementsByClassName(flyoutClassName); + + if (el.length === 0) { + // throw new Error(`Flyout with className "${flyoutClassName}" not found.`); + return null; + } + + return el.item(0) as HTMLDivElement; + }, [flyoutClassName]); + + const [panels, setPanels] = useState<{ [id: number]: Panel }>({}); + + const removePanel = useCallback((id: number) => { + setPanels((prev) => { + const { [id]: panelToRemove, ...rest } = prev; + return rest; + }); + }, []); + + const addPanel = useCallback( + (panel: Panel) => { + const nextId = panelId(); + setPanels((prev) => { + return { ...prev, [nextId]: panel }; + }); + return removePanel.bind(null, nextId); + }, + [removePanel] + ); + + const ctx: Context = useMemo( + () => ({ + addPanel, + }), + [addPanel] + ); + + useLayoutEffect(() => { + if (!flyoutDOMelement) { + return; + } + + const totalPercentWidth = Math.min( + 100, + Object.values(panels).reduce((acc, { width = 0 }) => acc + width, 0) + ); + const currentWidth = (maxWidth * totalPercentWidth) / 100; + + flyoutDOMelement.style.maxWidth = `${currentWidth}px`; + }, [panels, maxWidth, flyoutClassName, flyoutDOMelement]); + + return ( + + + + ); +}; + +export const useFlyoutPanelsContext = (): Context => { + const ctx = useContext(flyoutPanelsContext); + + if (ctx === undefined) { + throw new Error(' must be used within a wrapper'); + } + + return ctx; +}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/loading_state.test.tsx b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_content.tsx similarity index 50% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/loading_state.test.tsx rename to src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_content.tsx index ac10848500f63..a25d4a03a83c8 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/loading_state.test.tsx +++ b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_content.tsx @@ -5,15 +5,16 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import React, { useEffect } from 'react'; -import React from 'react'; -import { LoadingState } from '../loading_state'; -import { shallow } from 'enzyme'; +import { useFlyoutPanelContext } from './flyout_panel'; -describe('LoadingState', () => { - it('should render normally', () => { - const component = shallow(); +export const PanelContent: React.FC = (props) => { + const { registerContent } = useFlyoutPanelContext(); - expect(component).toMatchSnapshot(); - }); -}); + useEffect(() => { + registerContent(); + }, [registerContent]); + + return
; +}; diff --git a/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_footer.tsx b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_footer.tsx new file mode 100644 index 0000000000000..8a987420dd84b --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_footer.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { useEffect } from 'react'; +import { EuiFlyoutFooter, EuiFlyoutFooterProps } from '@elastic/eui'; + +import { useFlyoutPanelContext } from './flyout_panel'; + +export const PanelFooter: React.FC< + { children: React.ReactNode } & Omit +> = (props) => { + const { registerFooter } = useFlyoutPanelContext(); + + useEffect(() => { + registerFooter(); + }, [registerFooter]); + + return ; +}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.test.tsx b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_header.tsx similarity index 53% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.test.tsx rename to src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_header.tsx index 444e5c49cb497..00edf1c637fc1 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.test.tsx +++ b/src/plugins/index_pattern_editor/public/components/flyout_panels/flyout_panels_header.tsx @@ -7,13 +7,13 @@ */ import React from 'react'; -import { Header } from '../header'; -import { shallow } from 'enzyme'; +import { EuiSpacer, EuiFlyoutHeader, EuiFlyoutHeaderProps } from '@elastic/eui'; -describe('Header', () => { - it('should render normally', () => { - const component = shallow(
); - - expect(component).toMatchSnapshot(); - }); -}); +export const PanelHeader: React.FunctionComponent< + { children: React.ReactNode } & Omit +> = (props) => ( + <> + + + +); diff --git a/src/plugins/index_pattern_editor/public/components/flyout_panels/index.ts b/src/plugins/index_pattern_editor/public/components/flyout_panels/index.ts new file mode 100644 index 0000000000000..9fc9a7a916f56 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/flyout_panels/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PanelFooter } from './flyout_panels_footer'; +import { PanelHeader } from './flyout_panels_header'; +import { PanelContent } from './flyout_panels_content'; +import { Panel } from './flyout_panel'; +import { Panels } from './flyout_panels'; + +export const FlyoutPanels = { + Group: Panels, + Item: Panel, + Content: PanelContent, + Header: PanelHeader, + Footer: PanelFooter, +}; diff --git a/src/plugins/index_pattern_editor/public/components/footer/footer.tsx b/src/plugins/index_pattern_editor/public/components/footer/footer.tsx new file mode 100644 index 0000000000000..0c80d0e77534c --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/footer/footer.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; + +interface FooterProps { + onCancel: () => void; + onSubmit: () => void; + submitDisabled: boolean; +} + +const closeButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutCloseButtonLabel', { + defaultMessage: 'Close', +}); + +const saveButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutSaveButtonLabel', { + defaultMessage: 'Create index pattern', +}); + +export const Footer = ({ onCancel, onSubmit, submitDisabled }: FooterProps) => { + return ( + + + + + {closeButtonLabel} + + + + + + {saveButtonLabel} + + + + + ); +}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/index.ts b/src/plugins/index_pattern_editor/public/components/footer/index.ts similarity index 91% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/index.ts rename to src/plugins/index_pattern_editor/public/components/footer/index.ts index 8f737b3a42613..e3103aab170e0 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/index.ts +++ b/src/plugins/index_pattern_editor/public/components/footer/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { Header } from './header'; +export { Footer } from './footer'; diff --git a/src/plugins/index_pattern_editor/public/components/form_fields/index.ts b/src/plugins/index_pattern_editor/public/components/form_fields/index.ts new file mode 100644 index 0000000000000..0d2091c065771 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/form_fields/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { TimestampField } from './timestamp_field'; +export { TypeField } from './type_field'; +export { TitleField } from './title_field'; diff --git a/src/plugins/index_pattern_editor/public/components/form_fields/timestamp_field.tsx b/src/plugins/index_pattern_editor/public/components/form_fields/timestamp_field.tsx new file mode 100644 index 0000000000000..93cb39ea1ba33 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/form_fields/timestamp_field.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiFormRow, EuiComboBox, EuiFormHelpText, EuiComboBoxOptionOption } from '@elastic/eui'; + +import { + UseField, + FieldConfig, + ValidationConfig, + getFieldValidityAndErrorMessage, +} from '../../shared_imports'; + +import { TimestampOption } from '../../types'; +import { schema } from '../form_schema'; + +interface Props { + options: TimestampOption[]; + isLoadingOptions: boolean; + isExistingIndexPattern: boolean; + isLoadingMatchedIndices: boolean; + hasMatchedIndices: boolean; +} + +const requireTimestampOptionValidator = (options: Props['options']): ValidationConfig => ({ + validator: async ({ value }) => { + const isValueRequired = !!options.length; + if (isValueRequired && !value) { + return { + message: i18n.translate( + 'indexPatternEditor.requireTimestampOption.ValidationErrorMessage', + { + defaultMessage: 'Select a timestamp field.', + } + ), + }; + } + }, +}); + +const getTimestampConfig = ( + options: Props['options'] +): FieldConfig> => { + const timestampFieldConfig = schema.timestampField; + + const validations = [ + ...timestampFieldConfig.validations, + // note this is responsible for triggering the state update for the selected source list. + requireTimestampOptionValidator(options), + ]; + + return { + ...timestampFieldConfig!, + validations, + }; +}; + +const noTimestampOptionText = i18n.translate('indexPatternEditor.editor.form.noTimeFieldsLabel', { + defaultMessage: 'No matching data stream, index, or alias has a timestamp field.', +}); + +const timestampFieldHelp = i18n.translate('indexPatternEditor.editor.form.timeFieldHelp', { + defaultMessage: 'Select a timestamp field for use with the global time filter.', +}); + +export const TimestampField = ({ + options = [], + isLoadingOptions = false, + isExistingIndexPattern, + isLoadingMatchedIndices, + hasMatchedIndices, +}: Props) => { + const optionsAsComboBoxOptions = options.map(({ display, fieldName }) => ({ + label: display, + value: fieldName, + })); + const timestampConfig = useMemo(() => getTimestampConfig(options), [options]); + const selectTimestampHelp = options.length ? timestampFieldHelp : ''; + + const timestampNoFieldsHelp = + options.length === 0 && + !isExistingIndexPattern && + !isLoadingMatchedIndices && + !isLoadingOptions && + hasMatchedIndices + ? noTimestampOptionText + : ''; + + return ( + > config={timestampConfig} path="timestampField"> + {(field) => { + const { label, value, setValue } = field; + + if (value === undefined) { + return null; + } + + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const isDisabled = !optionsAsComboBoxOptions.length; + + return ( + <> + + <> + + placeholder={i18n.translate( + 'indexPatternEditor.editor.form.runtimeType.placeholderLabel', + { + defaultMessage: 'Select a timestamp field', + } + )} + singleSelection={{ asPlainText: true }} + options={optionsAsComboBoxOptions} + selectedOptions={value ? [value] : undefined} + onChange={(newValue) => { + if (newValue.length === 0) { + // Don't allow clearing the type. One must always be selected + return; + } + // + setValue(newValue[0]); + }} + isClearable={false} + isDisabled={isDisabled} + data-test-subj="timestampField" + aria-label={i18n.translate( + 'indexPatternEditor.editor.form.timestampSelectAriaLabel', + { + defaultMessage: 'Timestamp field', + } + )} + isLoading={isLoadingOptions} + fullWidth + /> + + {timestampNoFieldsHelp || selectTimestampHelp || <> } + + + + + ); + }} + + ); +}; diff --git a/src/plugins/index_pattern_editor/public/components/form_fields/title_field.tsx b/src/plugins/index_pattern_editor/public/components/form_fields/title_field.tsx new file mode 100644 index 0000000000000..aa16351e0922f --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/form_fields/title_field.tsx @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { ChangeEvent, useState, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { + UseField, + getFieldValidityAndErrorMessage, + ValidationConfig, + FieldConfig, +} from '../../shared_imports'; +import { canAppendWildcard } from '../../lib'; +import { schema } from '../form_schema'; +import { + MatchedItem, + RollupIndicesCapsResponse, + IndexPatternConfig, + MatchedIndicesSet, +} from '../../types'; + +interface RefreshMatchedIndicesResult { + matchedIndicesResult: MatchedIndicesSet; + newRollupIndexName?: string; +} + +interface TitleFieldProps { + existingIndexPatterns: string[]; + isRollup: boolean; + matchedIndices: MatchedItem[]; + rollupIndicesCapabilities: RollupIndicesCapsResponse; + refreshMatchedIndices: (title: string) => Promise; +} + +const rollupIndexPatternNoMatchError = { + message: i18n.translate('indexPatternEditor.rollupIndexPattern.createIndex.noMatchError', { + defaultMessage: 'Rollup index pattern error: must match one rollup index', + }), +}; + +const rollupIndexPatternTooManyMatchesError = { + message: i18n.translate('indexPatternEditor.rollupIndexPattern.createIndex.tooManyMatchesError', { + defaultMessage: 'Rollup index pattern error: can only match one rollup index', + }), +}; + +const mustMatchError = { + message: i18n.translate('indexPatternEditor.createIndex.noMatch', { + defaultMessage: 'Name must match one or more data streams, indices, or aliases.', + }), +}; + +const createTitlesNoDupesValidator = ( + namesNotAllowed: string[] +): ValidationConfig<{}, string, string> => ({ + validator: ({ value }) => { + if (namesNotAllowed.includes(value)) { + return { + message: i18n.translate('indexPatternEditor.indexPatternExists.ValidationErrorMessage', { + defaultMessage: 'An index pattern with this title already exists.', + }), + }; + } + }, +}); + +interface MatchesValidatorArgs { + rollupIndicesCapabilities: Record; + refreshMatchedIndices: (title: string) => Promise; + isRollup: boolean; +} + +const createMatchesIndicesValidator = ({ + rollupIndicesCapabilities, + refreshMatchedIndices, + isRollup, +}: MatchesValidatorArgs): ValidationConfig<{}, string, string> => ({ + validator: async ({ value }) => { + const { matchedIndicesResult, newRollupIndexName } = await refreshMatchedIndices(value); + const rollupIndices = Object.keys(rollupIndicesCapabilities); + + if (matchedIndicesResult.exactMatchedIndices.length === 0) { + return mustMatchError; + } + + if (!isRollup || !rollupIndices || !rollupIndices.length) { + return; + } + + // A rollup index pattern needs to match one and only one rollup index. + const rollupIndexMatches = matchedIndicesResult.exactMatchedIndices.filter((matchedIndex) => + rollupIndices.includes(matchedIndex.name) + ); + + if (!rollupIndexMatches.length) { + return rollupIndexPatternNoMatchError; + } else if (rollupIndexMatches.length > 1) { + return rollupIndexPatternTooManyMatchesError; + } + + // Error info is potentially provided via the rollup indices caps request + const error = newRollupIndexName && rollupIndicesCapabilities[newRollupIndexName].error; + + if (error) { + return { + message: i18n.translate('indexPatternEditor.rollup.uncaughtError', { + defaultMessage: 'Rollup index pattern error: {error}', + values: { + error, + }, + }), + }; + } + }, +}); + +interface GetTitleConfigArgs { + namesNotAllowed: string[]; + isRollup: boolean; + matchedIndices: MatchedItem[]; + rollupIndicesCapabilities: RollupIndicesCapsResponse; + refreshMatchedIndices: (title: string) => Promise; +} + +const getTitleConfig = ({ + namesNotAllowed, + isRollup, + rollupIndicesCapabilities, + refreshMatchedIndices, +}: GetTitleConfigArgs): FieldConfig => { + const titleFieldConfig = schema.title; + + const validations = [ + ...titleFieldConfig.validations, + // note this is responsible for triggering the state update for the selected source list. + createMatchesIndicesValidator({ + rollupIndicesCapabilities, + refreshMatchedIndices, + isRollup, + }), + createTitlesNoDupesValidator(namesNotAllowed), + ]; + + return { + ...titleFieldConfig!, + validations, + }; +}; + +export const TitleField = ({ + existingIndexPatterns, + isRollup, + matchedIndices, + rollupIndicesCapabilities, + refreshMatchedIndices, +}: TitleFieldProps) => { + const [appendedWildcard, setAppendedWildcard] = useState(false); + + const fieldConfig = useMemo( + () => + getTitleConfig({ + namesNotAllowed: existingIndexPatterns, + isRollup, + matchedIndices, + rollupIndicesCapabilities, + refreshMatchedIndices, + }), + [ + existingIndexPatterns, + isRollup, + matchedIndices, + rollupIndicesCapabilities, + refreshMatchedIndices, + ] + ); + + return ( + + path="title" + config={fieldConfig} + componentProps={{ + euiFieldProps: { + 'aria-label': i18n.translate('indexPatternEditor.form.titleAriaLabel', { + defaultMessage: 'Title field', + }), + }, + }} + > + {(field) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + return ( + + ) => { + e.persist(); + let query = e.target.value; + if (query.length === 1 && !appendedWildcard && canAppendWildcard(query)) { + query += '*'; + setAppendedWildcard(true); + setTimeout(() => e.target.setSelectionRange(1, 1)); + } else { + if (['', '*'].includes(query) && appendedWildcard) { + query = ''; + setAppendedWildcard(false); + } + } + field.setValue(query); + }} + isLoading={field.isValidating} + fullWidth + data-test-subj="createIndexPatternNameInput" + /> + + ); + }} + + ); +}; diff --git a/src/plugins/index_pattern_editor/public/components/form_fields/type_field.tsx b/src/plugins/index_pattern_editor/public/components/form_fields/type_field.tsx new file mode 100644 index 0000000000000..606197159e53c --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/form_fields/type_field.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { euiColorAccent } from '@elastic/eui/dist/eui_theme_light.json'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiFormRow, + EuiSuperSelect, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiBadge, +} from '@elastic/eui'; + +import { UseField } from '../../shared_imports'; + +import { INDEX_PATTERN_TYPE, IndexPatternConfig } from '../../types'; + +interface TypeFieldProps { + onChange: (type: INDEX_PATTERN_TYPE) => void; +} + +const standardSelectItem = ( + + + + + + + + +); + +const rollupSelectItem = ( + + + +   + + + + + + + + +); + +export const TypeField = ({ onChange }: TypeFieldProps) => { + return ( + path="type"> + {({ label, value, setValue }) => { + if (value === undefined) { + return null; + } + return ( + <> + + { + setValue(newValue); + onChange(newValue); + }} + aria-label={i18n.translate('indexPatternEditor.editor.form.typeSelectAriaLabel', { + defaultMessage: 'Type field', + })} + fullWidth + /> + + + ); + }} + + ); +}; diff --git a/src/plugins/index_pattern_editor/public/components/form_schema.ts b/src/plugins/index_pattern_editor/public/components/form_schema.ts new file mode 100644 index 0000000000000..4e852285031ca --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/form_schema.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { fieldValidators } from '../shared_imports'; +import { INDEX_PATTERN_TYPE } from '../types'; + +export const schema = { + title: { + label: i18n.translate('indexPatternEditor.editor.form.titleLabel', { + defaultMessage: 'Name', + }), + defaultValue: '', + helpText: i18n.translate('indexPatternEditor.validations.titleHelpText', { + defaultMessage: + 'Use an asterisk (*) to match multiple indices. Spaces and the characters , /, ?, ", <, >, | are not allowed.', + }), + validations: [ + { + validator: fieldValidators.emptyField( + i18n.translate('indexPatternEditor.validations.titleIsRequiredErrorMessage', { + defaultMessage: 'A name is required.', + }) + ), + }, + ], + }, + timestampField: { + label: i18n.translate('indexPatternEditor.editor.form.timeFieldLabel', { + defaultMessage: 'Timestamp field', + }), + helpText: i18n.translate('indexPatternEditor.editor.form.timestampFieldHelp', { + defaultMessage: 'Select a timestamp field for use with the global time filter.', + }), + validations: [], + }, + allowHidden: { + label: i18n.translate('indexPatternEditor.editor.form.allowHiddenLabel', { + defaultMessage: 'Allow hidden and system indices', + }), + defaultValue: false, + }, + id: { + label: i18n.translate('indexPatternEditor.editor.form.customIdLabel', { + defaultMessage: 'Custom index pattern ID', + }), + helpText: i18n.translate('indexPatternEditor.editor.form.customIdHelp', { + defaultMessage: + 'Kibana provides a unique identifier for each index pattern, or you can create your own.', + }), + }, + type: { + label: i18n.translate('indexPatternEditor.editor.form.TypeLabel', { + defaultMessage: 'Index pattern type', + }), + defaultValue: INDEX_PATTERN_TYPE.DEFAULT, + }, +}; diff --git a/src/plugins/index_pattern_editor/public/components/i18n_texts.ts b/src/plugins/index_pattern_editor/public/components/i18n_texts.ts new file mode 100644 index 0000000000000..5745714ae7bd8 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/i18n_texts.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +export const geti18nTexts = () => { + return { + noTimestampOptionText: i18n.translate( + 'indexPatternEditor.createIndexPattern.stepTime.noTimeFieldsLabel', + { + defaultMessage: 'No matching data stream, index, or alias has a timestamp field.', + } + ), + timestampFieldHelp: i18n.translate('indexPatternEditor.editor.form.timeFieldHelp', { + defaultMessage: 'Select a timestamp field for use with the global time filter.', + }), + rollupLabel: i18n.translate('indexPatternEditor.rollupIndexPattern.createIndex.indexLabel', { + defaultMessage: 'Rollup', + }), + }; +}; diff --git a/src/plugins/index_pattern_editor/public/components/index.ts b/src/plugins/index_pattern_editor/public/components/index.ts new file mode 100644 index 0000000000000..82da708ac859d --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/index.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + IndexPatternEditorFlyoutContent, + Props as IndexPatternEditorFlyoutContentProps, +} from './index_pattern_editor_flyout_content'; + +export { IndexPatternEditor } from './index_pattern_editor'; + +export { schema } from './form_schema'; +export { TimestampField, TypeField, TitleField } from './form_fields'; +export { EmptyPrompts } from './empty_prompts'; +export { PreviewPanel } from './preview_panel'; +export { LoadingIndices } from './loading_indices'; +export { geti18nTexts } from './i18n_texts'; +export { Footer } from './footer'; +export { AdvancedParamsContent } from './advanced_params_content'; +export { RollupBetaWarning } from './rollup_beta_warning'; diff --git a/src/plugins/index_pattern_editor/public/components/index_pattern_editor.scss b/src/plugins/index_pattern_editor/public/components/index_pattern_editor.scss new file mode 100644 index 0000000000000..5b3a30effc767 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/index_pattern_editor.scss @@ -0,0 +1,13 @@ +.indexPatternEditor__form { + flex-grow: 1; +} + +.fieldEditor__mainFlyoutPanel { + display: flex; + flex-direction: column; +} + +.indexPatternEditor__footer { + margin-left: -$euiSizeL; + margin-right: -$euiSizeL; +} diff --git a/src/plugins/index_pattern_editor/public/components/index_pattern_editor.tsx b/src/plugins/index_pattern_editor/public/components/index_pattern_editor.tsx new file mode 100644 index 0000000000000..bc0b6156a7844 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/index_pattern_editor.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiFlyout } from '@elastic/eui'; +import { IndexPatternEditorLazy } from './index_pattern_editor_lazy'; +import { IndexPatternEditorContext, IndexPatternEditorProps } from '../types'; +import { createKibanaReactContext } from '../shared_imports'; +import './index_pattern_editor.scss'; + +export interface IndexPatternEditorPropsWithServices extends IndexPatternEditorProps { + services: IndexPatternEditorContext; +} + +export const IndexPatternEditor = ({ + onSave, + onCancel = () => {}, + services, + defaultTypeIsRollup = false, + requireTimestampField = false, +}: IndexPatternEditorPropsWithServices) => { + const { + Provider: KibanaReactContextProvider, + } = createKibanaReactContext(services); + + return ( + + {}} hideCloseButton={true} size="l"> + + + + ); +}; diff --git a/src/plugins/index_pattern_editor/public/components/index_pattern_editor_flyout_content.tsx b/src/plugins/index_pattern_editor/public/components/index_pattern_editor_flyout_content.tsx new file mode 100644 index 0000000000000..cabff9bfb009b --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/index_pattern_editor_flyout_content.tsx @@ -0,0 +1,379 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { + IndexPatternSpec, + Form, + useForm, + useFormData, + useKibana, + GetFieldsOptions, +} from '../shared_imports'; + +import { ensureMinimumTime, getIndices, extractTimeFields, getMatchedIndices } from '../lib'; +import { FlyoutPanels } from './flyout_panels'; + +import { + MatchedItem, + IndexPatternEditorContext, + RollupIndicesCapsResponse, + INDEX_PATTERN_TYPE, + IndexPatternConfig, + MatchedIndicesSet, + FormInternal, + TimestampOption, +} from '../types'; + +import { + TimestampField, + TypeField, + TitleField, + schema, + Footer, + AdvancedParamsContent, + EmptyPrompts, + PreviewPanel, + RollupBetaWarning, +} from '.'; + +export interface Props { + /** + * Handler for the "save" footer button + */ + onSave: (indexPatternSpec: IndexPatternSpec) => void; + /** + * Handler for the "cancel" footer button + */ + onCancel: () => void; + defaultTypeIsRollup?: boolean; + requireTimestampField?: boolean; +} + +const editorTitle = i18n.translate('indexPatternEditor.title', { + defaultMessage: 'Create index pattern', +}); + +const IndexPatternEditorFlyoutContentComponent = ({ + onSave, + onCancel, + defaultTypeIsRollup, + requireTimestampField = false, +}: Props) => { + const isMounted = useRef(false); + const { + services: { http, indexPatternService, uiSettings }, + } = useKibana(); + + const { form } = useForm({ + defaultValue: { + type: defaultTypeIsRollup ? INDEX_PATTERN_TYPE.ROLLUP : INDEX_PATTERN_TYPE.DEFAULT, + }, + schema, + onSubmit: async (formData, isValid) => { + if (!isValid) { + return; + } + + const indexPatternStub: IndexPatternSpec = { + title: formData.title, + timeFieldName: formData.timestampField?.value, + id: formData.id, + }; + + if (type === INDEX_PATTERN_TYPE.ROLLUP && rollupIndex) { + indexPatternStub.type = INDEX_PATTERN_TYPE.ROLLUP; + indexPatternStub.typeMeta = { + params: { + rollup_index: rollupIndex, + }, + aggs: rollupIndicesCapabilities[rollupIndex].aggs, + }; + } + + await onSave(indexPatternStub); + }, + }); + + const { getFields } = form; + + const [{ title, allowHidden, type }] = useFormData({ form }); + const [isLoadingSources, setIsLoadingSources] = useState(true); + + const [timestampFieldOptions, setTimestampFieldOptions] = useState([]); + const [isLoadingTimestampFields, setIsLoadingTimestampFields] = useState(false); + const [isLoadingMatchedIndices, setIsLoadingMatchedIndices] = useState(false); + const [allSources, setAllSources] = useState([]); + const [isLoadingIndexPatterns, setIsLoadingIndexPatterns] = useState(true); + const [existingIndexPatterns, setExistingIndexPatterns] = useState([]); + const [rollupIndex, setRollupIndex] = useState(); + const [ + rollupIndicesCapabilities, + setRollupIndicesCapabilities, + ] = useState({}); + const [matchedIndices, setMatchedIndices] = useState({ + allIndices: [], + exactMatchedIndices: [], + partialMatchedIndices: [], + visibleIndices: [], + }); + + // load all data sources and set initial matchedIndices + const loadSources = useCallback(() => { + getIndices(http, () => false, '*', allowHidden).then((dataSources) => { + setAllSources(dataSources); + const matchedSet = getMatchedIndices(dataSources, [], [], allowHidden); + setMatchedIndices(matchedSet); + setIsLoadingSources(false); + }); + }, [http, allowHidden]); + + // loading list of index patterns + useEffect(() => { + isMounted.current = true; + loadSources(); + const getTitles = async () => { + const indexPatternTitles = await indexPatternService.getTitles(); + if (isMounted.current) { + setExistingIndexPatterns(indexPatternTitles); + setIsLoadingIndexPatterns(false); + } + }; + getTitles(); + return () => { + isMounted.current = false; + }; + }, [http, indexPatternService, loadSources]); + + // loading rollup info + useEffect(() => { + const getRollups = async () => { + try { + const response = await http.get('/api/rollup/indices'); + if (isMounted.current) { + setRollupIndicesCapabilities(response || {}); + } + } catch (e) { + // Silently swallow failure responses such as expired trials + } + }; + + getRollups(); + }, [http, type]); + + const getRollupIndices = (rollupCaps: RollupIndicesCapsResponse) => Object.keys(rollupCaps); + + const loadTimestampFieldOptions = useCallback( + async (query: string) => { + let timestampOptions: TimestampOption[] = []; + const isValidResult = + !existingIndexPatterns.includes(query) && matchedIndices.exactMatchedIndices.length > 0; + if (isValidResult) { + setIsLoadingTimestampFields(true); + const getFieldsOptions: GetFieldsOptions = { + pattern: query, + }; + if (type === INDEX_PATTERN_TYPE.ROLLUP) { + getFieldsOptions.type = INDEX_PATTERN_TYPE.ROLLUP; + getFieldsOptions.rollupIndex = rollupIndex; + } + + const fields = await ensureMinimumTime( + indexPatternService.getFieldsForWildcard(getFieldsOptions) + ); + timestampOptions = extractTimeFields(fields, requireTimestampField); + } + if (isMounted.current) { + setIsLoadingTimestampFields(false); + setTimestampFieldOptions(timestampOptions); + } + return timestampOptions; + }, + [ + existingIndexPatterns, + indexPatternService, + requireTimestampField, + rollupIndex, + type, + matchedIndices.exactMatchedIndices, + ] + ); + + useEffect(() => { + loadTimestampFieldOptions(title); + getFields().timestampField?.setValue(''); + }, [matchedIndices, loadTimestampFieldOptions, title, getFields]); + + const reloadMatchedIndices = useCallback( + async (newTitle: string) => { + const isRollupIndex = (indexName: string) => + getRollupIndices(rollupIndicesCapabilities).includes(indexName); + let newRollupIndexName: string | undefined; + + const fetchIndices = async (query: string = '') => { + setIsLoadingMatchedIndices(true); + const indexRequests = []; + + if (query?.endsWith('*')) { + const exactMatchedQuery = getIndices(http, isRollupIndex, query, allowHidden); + indexRequests.push(exactMatchedQuery); + // provide default value when not making a request for the partialMatchQuery + indexRequests.push(Promise.resolve([])); + } else { + const exactMatchQuery = getIndices(http, isRollupIndex, query, allowHidden); + const partialMatchQuery = getIndices(http, isRollupIndex, `${query}*`, allowHidden); + + indexRequests.push(exactMatchQuery); + indexRequests.push(partialMatchQuery); + } + + const [exactMatched, partialMatched] = (await ensureMinimumTime( + indexRequests + )) as MatchedItem[][]; + + const matchedIndicesResult = getMatchedIndices( + allSources, + partialMatched, + exactMatched, + allowHidden + ); + + if (isMounted.current) { + if (type === INDEX_PATTERN_TYPE.ROLLUP) { + const rollupIndices = exactMatched.filter((index) => isRollupIndex(index.name)); + newRollupIndexName = rollupIndices.length === 1 ? rollupIndices[0].name : undefined; + setRollupIndex(newRollupIndexName); + } else { + setRollupIndex(undefined); + } + + setMatchedIndices(matchedIndicesResult); + setIsLoadingMatchedIndices(false); + } + + return { matchedIndicesResult, newRollupIndexName }; + }; + + return fetchIndices(newTitle); + }, + [http, allowHidden, allSources, type, rollupIndicesCapabilities] + ); + + useEffect(() => { + reloadMatchedIndices(title); + }, [allowHidden, reloadMatchedIndices, title]); + + const onTypeChange = useCallback( + (newType) => { + form.setFieldValue('title', ''); + form.setFieldValue('timestampField', ''); + if (newType === INDEX_PATTERN_TYPE.ROLLUP) { + form.setFieldValue('allowHidden', false); + } + }, + [form] + ); + + if (isLoadingSources || isLoadingIndexPatterns) { + return ; + } + + const showIndexPatternTypeSelect = () => + uiSettings.isDeclared('rollups:enableIndexPatterns') && + uiSettings.get('rollups:enableIndexPatterns') && + getRollupIndices(rollupIndicesCapabilities).length; + + const indexPatternTypeSelect = showIndexPatternTypeSelect() ? ( + <> + + + + + + + {type === INDEX_PATTERN_TYPE.ROLLUP ? ( + + + + + + ) : ( + <> + )} + + ) : ( + <> + ); + + return ( + + + + +

{editorTitle}

+
+
+ {indexPatternTypeSelect} + + + + + + + + + + + + + + +
form.submit()} + submitDisabled={form.isSubmitted && !form.isValid} + /> + + + {isLoadingSources ? ( + <> + ) : ( + + )} + + + + ); +}; + +export const IndexPatternEditorFlyoutContent = React.memo(IndexPatternEditorFlyoutContentComponent); diff --git a/src/plugins/index_pattern_editor/public/components/index_pattern_editor_lazy.tsx b/src/plugins/index_pattern_editor/public/components/index_pattern_editor_lazy.tsx new file mode 100644 index 0000000000000..4241f042af0d5 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/index_pattern_editor_lazy.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; + +import { IndexPatternEditorProps } from '../types'; + +const IndexPatternFlyoutContentContainer = lazy( + () => import('./index_pattern_flyout_content_container') +); + +export const IndexPatternEditorLazy = (props: IndexPatternEditorProps) => ( + }> + + +); diff --git a/src/plugins/index_pattern_editor/public/components/index_pattern_flyout_content_container.tsx b/src/plugins/index_pattern_editor/public/components/index_pattern_flyout_content_container.tsx new file mode 100644 index 0000000000000..2f60d85677207 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/index_pattern_flyout_content_container.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { IndexPatternSpec, useKibana } from '../shared_imports'; +import { IndexPatternEditorFlyoutContent } from './index_pattern_editor_flyout_content'; +import { IndexPatternEditorContext, IndexPatternEditorProps } from '../types'; + +const IndexPatternFlyoutContentContainer = ({ + onSave, + onCancel = () => {}, + defaultTypeIsRollup, + requireTimestampField = false, +}: IndexPatternEditorProps) => { + const { + services: { indexPatternService, notifications }, + } = useKibana(); + + const onSaveClick = async (indexPatternSpec: IndexPatternSpec) => { + try { + const indexPattern = await indexPatternService.createAndSave(indexPatternSpec); + + const message = i18n.translate('indexPatternEditor.saved', { + defaultMessage: "Saved '{indexPatternTitle}'", + values: { indexPatternTitle: indexPattern.title }, + }); + notifications.toasts.addSuccess(message); + await onSave(indexPattern); + } catch (e) { + const title = i18n.translate('indexPatternEditor.indexPatterns.unableSaveLabel', { + defaultMessage: 'Failed to save index pattern.', + }); + + notifications.toasts.addDanger({ title }); + } + }; + + return ( + + ); +}; + +/* eslint-disable import/no-default-export */ +export default IndexPatternFlyoutContentContainer; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/__snapshots__/loading_indices.test.tsx.snap b/src/plugins/index_pattern_editor/public/components/loading_indices/__snapshots__/loading_indices.test.tsx.snap similarity index 88% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/__snapshots__/loading_indices.test.tsx.snap rename to src/plugins/index_pattern_editor/public/components/loading_indices/__snapshots__/loading_indices.test.tsx.snap index a5517f6d4b616..8c62657c7859e 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/__snapshots__/loading_indices.test.tsx.snap +++ b/src/plugins/index_pattern_editor/public/components/loading_indices/__snapshots__/loading_indices.test.tsx.snap @@ -18,7 +18,7 @@ exports[`LoadingIndices should render normally 1`] = ` > diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/index.ts b/src/plugins/index_pattern_editor/public/components/loading_indices/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/index.ts rename to src/plugins/index_pattern_editor/public/components/loading_indices/index.ts diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/loading_indices.test.tsx b/src/plugins/index_pattern_editor/public/components/loading_indices/loading_indices.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/loading_indices.test.tsx rename to src/plugins/index_pattern_editor/public/components/loading_indices/loading_indices.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/loading_indices.tsx b/src/plugins/index_pattern_editor/public/components/loading_indices/loading_indices.tsx similarity index 93% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/loading_indices.tsx rename to src/plugins/index_pattern_editor/public/components/loading_indices/loading_indices.tsx index eb17027c63a3d..14ae51567938d 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/loading_indices/loading_indices.tsx +++ b/src/plugins/index_pattern_editor/public/components/loading_indices/loading_indices.tsx @@ -24,7 +24,7 @@ export const LoadingIndices = ({ ...rest }) => (

diff --git a/src/plugins/index_pattern_management/public/components/create_button/index.ts b/src/plugins/index_pattern_editor/public/components/preview_panel/index.ts similarity index 88% rename from src/plugins/index_pattern_management/public/components/create_button/index.ts rename to src/plugins/index_pattern_editor/public/components/preview_panel/index.ts index 497096d2fb7bc..c677d9878f183 100644 --- a/src/plugins/index_pattern_management/public/components/create_button/index.ts +++ b/src/plugins/index_pattern_editor/public/components/preview_panel/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { CreateButton } from './create_button'; +export { PreviewPanel } from './preview_panel'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap b/src/plugins/index_pattern_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap similarity index 97% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap rename to src/plugins/index_pattern_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap index ca41dddf6197e..74d803a6ff176 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap +++ b/src/plugins/index_pattern_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap @@ -47,7 +47,7 @@ exports[`IndicesList should change pages 1`] = ` > { pager: Pager; constructor(props: IndicesListProps) { @@ -96,7 +97,7 @@ export class IndicesList extends React.Component diff --git a/src/plugins/index_pattern_editor/public/components/preview_panel/preview_panel.tsx b/src/plugins/index_pattern_editor/public/components/preview_panel/preview_panel.tsx new file mode 100644 index 0000000000000..28413debdb2a9 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/preview_panel/preview_panel.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { StatusMessage } from './status_message'; +import { IndicesList } from './indices_list'; + +import { INDEX_PATTERN_TYPE, MatchedIndicesSet } from '../../types'; + +interface Props { + type: INDEX_PATTERN_TYPE; + allowHidden: boolean; + title: string; + matched: MatchedIndicesSet; +} + +export const PreviewPanel = ({ type, allowHidden, title = '', matched }: Props) => { + const indicesListContent = + matched.visibleIndices.length || matched.allIndices.length ? ( + <> + + + + ) : ( + <> + ); + + return ( + <> + + {indicesListContent} + + ); +}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/__snapshots__/status_message.test.tsx.snap b/src/plugins/index_pattern_editor/public/components/preview_panel/status_message/__snapshots__/status_message.test.tsx.snap similarity index 66% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/__snapshots__/status_message.test.tsx.snap rename to src/plugins/index_pattern_editor/public/components/preview_panel/status_message/__snapshots__/status_message.test.tsx.snap index 44b753c473803..8b492be417bff 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/__snapshots__/status_message.test.tsx.snap +++ b/src/plugins/index_pattern_editor/public/components/preview_panel/status_message/__snapshots__/status_message.test.tsx.snap @@ -11,7 +11,7 @@ exports[`StatusMessage should render with exact matches 1`] = `   @@ -134,7 +133,7 @@ exports[`StatusMessage should show that system indices exist 1`] = ` diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/index.ts b/src/plugins/index_pattern_editor/public/components/preview_panel/status_message/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/index.ts rename to src/plugins/index_pattern_editor/public/components/preview_panel/status_message/index.ts diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.test.tsx b/src/plugins/index_pattern_editor/public/components/preview_panel/status_message/status_message.test.tsx similarity index 96% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.test.tsx rename to src/plugins/index_pattern_editor/public/components/preview_panel/status_message/status_message.test.tsx index 6201440d5e7ff..1bae9bdf3c666 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.test.tsx +++ b/src/plugins/index_pattern_editor/public/components/preview_panel/status_message/status_message.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { StatusMessage } from '../status_message'; import { shallow } from 'enzyme'; -import { MatchedItem } from '../../../../types'; +import { MatchedItem } from '../../../types'; const tagsPartial = { tags: [], @@ -22,6 +22,7 @@ const matchedIndices = { ] as unknown) as MatchedItem[], exactMatchedIndices: [] as MatchedItem[], partialMatchedIndices: ([{ name: 'kibana', ...tagsPartial }] as unknown) as MatchedItem[], + visibleIndices: [], }; describe('StatusMessage', () => { @@ -94,6 +95,7 @@ describe('StatusMessage', () => { allIndices: [], exactMatchedIndices: [], partialMatchedIndices: [], + visibleIndices: [], }} isIncludingSystemIndices={false} query={''} @@ -111,6 +113,7 @@ describe('StatusMessage', () => { allIndices: [], exactMatchedIndices: [], partialMatchedIndices: [], + visibleIndices: [], }} isIncludingSystemIndices={true} query={''} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.tsx b/src/plugins/index_pattern_editor/public/components/preview_panel/status_message/status_message.tsx similarity index 58% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.tsx rename to src/plugins/index_pattern_editor/public/components/preview_panel/status_message/status_message.tsx index 0ab41ed1a5be4..871ed79c27de5 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.tsx +++ b/src/plugins/index_pattern_editor/public/components/preview_panel/status_message/status_message.tsx @@ -12,19 +12,48 @@ import { EuiCallOut } from '@elastic/eui'; import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; import { FormattedMessage } from '@kbn/i18n/react'; -import { MatchedItem } from '../../../../types'; +import { MatchedIndicesSet } from '../../../types'; interface StatusMessageProps { - matchedIndices: { - allIndices: MatchedItem[]; - exactMatchedIndices: MatchedItem[]; - partialMatchedIndices: MatchedItem[]; - }; + matchedIndices: MatchedIndicesSet; isIncludingSystemIndices: boolean; query: string; showSystemIndices: boolean; } +const NoMatchStatusMessage = (allIndicesLength: number) => ( + + + + + ), + }} + /> + +); + +const NoMatchNoIndicesStatusMessage = () => ( + + + +); + export const StatusMessage: React.FC = ({ matchedIndices: { allIndices = [], exactMatchedIndices = [], partialMatchedIndices = [] }, isIncludingSystemIndices, @@ -45,10 +74,10 @@ export const StatusMessage: React.FC = ({ statusMessage = ( @@ -58,8 +87,8 @@ export const StatusMessage: React.FC = ({ statusMessage = ( ); @@ -67,7 +96,7 @@ export const StatusMessage: React.FC = ({ statusMessage = ( @@ -80,7 +109,7 @@ export const StatusMessage: React.FC = ({   @@ -124,33 +153,9 @@ export const StatusMessage: React.FC = ({ } else { statusIcon = undefined; statusColor = 'warning'; - statusMessage = ( - - - - - ), - indicesLength: allIndicesLength, - }} - /> - - ); + statusMessage = allIndicesLength + ? NoMatchStatusMessage(allIndicesLength) + : NoMatchNoIndicesStatusMessage(); } return ( diff --git a/src/plugins/index_pattern_editor/public/components/rollup_beta_warning/index.ts b/src/plugins/index_pattern_editor/public/components/rollup_beta_warning/index.ts new file mode 100644 index 0000000000000..4c72c12312855 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/rollup_beta_warning/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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { RollupBetaWarning } from './rollup_beta_warning'; diff --git a/src/plugins/index_pattern_editor/public/components/rollup_beta_warning/rollup_beta_warning.tsx b/src/plugins/index_pattern_editor/public/components/rollup_beta_warning/rollup_beta_warning.tsx new file mode 100644 index 0000000000000..fcd61bab07d4c --- /dev/null +++ b/src/plugins/index_pattern_editor/public/components/rollup_beta_warning/rollup_beta_warning.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EuiCallOut } from '@elastic/eui'; + +const rollupBetaWarningTitle = i18n.translate( + 'indexPatternEditor.rollupIndexPattern.warning.title', + { + defaultMessage: 'Beta feature', + } +); + +export const RollupBetaWarning = () => ( + +

+ +

+

+ +

+
+); diff --git a/src/plugins/index_pattern_editor/public/constants.ts b/src/plugins/index_pattern_editor/public/constants.ts new file mode 100644 index 0000000000000..ff74e0827fa50 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const pluginName = 'index_pattern_editor'; +export const MAX_NUMBER_OF_MATCHING_INDICES = 100; +export const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns'; diff --git a/src/plugins/index_pattern_editor/public/index.ts b/src/plugins/index_pattern_editor/public/index.ts new file mode 100644 index 0000000000000..76c184f9399f1 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Management Plugin - public + * + * This is the entry point for the entire client-side public contract of the plugin. + * If something is not explicitly exported here, you can safely assume it is private + * to the plugin and not considered stable. + * + * All stateful contracts will be injected by the platform at runtime, and are defined + * in the setup/start interfaces in `plugin.ts`. The remaining items exported here are + * either types, or static code. + */ + +import { IndexPatternEditorPlugin } from './plugin'; + +export type { PluginStart as IndexPatternEditorStart, IndexPatternEditorProps } from './types'; + +export function plugin() { + return new IndexPatternEditorPlugin(); +} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/__snapshots__/get_indices.test.ts.snap b/src/plugins/index_pattern_editor/public/lib/__snapshots__/get_indices.test.ts.snap similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/__snapshots__/get_indices.test.ts.snap rename to src/plugins/index_pattern_editor/public/lib/__snapshots__/get_indices.test.ts.snap diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/can_append_wildcard.test.ts b/src/plugins/index_pattern_editor/public/lib/can_append_wildcard.test.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/can_append_wildcard.test.ts rename to src/plugins/index_pattern_editor/public/lib/can_append_wildcard.test.ts diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/can_append_wildcard.ts b/src/plugins/index_pattern_editor/public/lib/can_append_wildcard.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/can_append_wildcard.ts rename to src/plugins/index_pattern_editor/public/lib/can_append_wildcard.ts diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/contains_illegal_characters.ts b/src/plugins/index_pattern_editor/public/lib/contains_illegal_characters.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/contains_illegal_characters.ts rename to src/plugins/index_pattern_editor/public/lib/contains_illegal_characters.ts diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/contains_invalid_characters.test.ts b/src/plugins/index_pattern_editor/public/lib/contains_invalid_characters.test.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/contains_invalid_characters.test.ts rename to src/plugins/index_pattern_editor/public/lib/contains_invalid_characters.test.ts diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.test.ts b/src/plugins/index_pattern_editor/public/lib/ensure_minimum_time.test.ts similarity index 79% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.test.ts rename to src/plugins/index_pattern_editor/public/lib/ensure_minimum_time.test.ts index 1bda1b9b5394c..6fb80df3f8932 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.test.ts +++ b/src/plugins/index_pattern_editor/public/lib/ensure_minimum_time.test.ts @@ -9,28 +9,25 @@ import { ensureMinimumTime } from './ensure_minimum_time'; describe('ensureMinimumTime', () => { - it('resolves single promise', async (done) => { + it('resolves single promise', async () => { const promiseA = new Promise((resolve) => resolve('a')); const a = await ensureMinimumTime(promiseA, 0); expect(a).toBe('a'); - done(); }); - it('resolves multiple promises', async (done) => { - const promiseA = new Promise((resolve) => resolve('a')); - const promiseB = new Promise((resolve) => resolve('b')); + it('resolves multiple promises', async () => { + const promiseA = new Promise((resolve) => resolve('a')); + const promiseB = new Promise((resolve) => resolve('b')); const [a, b] = await ensureMinimumTime([promiseA, promiseB], 0); expect(a).toBe('a'); expect(b).toBe('b'); - done(); }); - it('resolves in the amount of time provided, at minimum', async (done) => { + it('resolves in the amount of time provided, at minimum', async () => { const startTime = new Date().getTime(); const promise = new Promise((resolve) => resolve()); await ensureMinimumTime(promise, 100); const endTime = new Date().getTime(); expect(endTime - startTime).toBeGreaterThanOrEqual(100); - done(); }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.ts b/src/plugins/index_pattern_editor/public/lib/ensure_minimum_time.ts similarity index 95% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.ts rename to src/plugins/index_pattern_editor/public/lib/ensure_minimum_time.ts index cab11ff3b5a37..5c52446efbe34 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/ensure_minimum_time.ts +++ b/src/plugins/index_pattern_editor/public/lib/ensure_minimum_time.ts @@ -15,8 +15,8 @@ export const DEFAULT_MINIMUM_TIME_MS = 300; -export async function ensureMinimumTime( - promiseOrPromises: Promise | Array>, +export async function ensureMinimumTime( + promiseOrPromises: Promise | Array>, minimumTimeMs = DEFAULT_MINIMUM_TIME_MS ) { let returnValue; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.test.ts b/src/plugins/index_pattern_editor/public/lib/extract_time_fields.test.ts similarity index 76% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.test.ts rename to src/plugins/index_pattern_editor/public/lib/extract_time_fields.test.ts index 071c21d229524..bc3e4209f5db7 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.test.ts +++ b/src/plugins/index_pattern_editor/public/lib/extract_time_fields.test.ts @@ -16,18 +16,16 @@ describe('extractTimeFields', () => { { type: 'text', name: 'name' }, ] as IndexPatternField[]; - expect(extractTimeFields(fields)).toEqual([ - { display: `The indices which match this index pattern don't contain any time fields.` }, - ]); + expect(extractTimeFields(fields)).toEqual([]); }); it('should add extra options', () => { const fields = [{ type: 'date', name: '@timestamp' }] as IndexPatternField[]; + // const extractedFields = extractTimeFields(fields); expect(extractTimeFields(fields)).toEqual([ { display: '@timestamp', fieldName: '@timestamp' }, - { isDisabled: true, display: '───', fieldName: '' }, - { display: `I don't want to use the time filter`, fieldName: undefined }, + { display: `--- I don't want to use the time filter ---`, fieldName: '' }, ]); }); }); diff --git a/src/plugins/index_pattern_editor/public/lib/extract_time_fields.ts b/src/plugins/index_pattern_editor/public/lib/extract_time_fields.ts new file mode 100644 index 0000000000000..82b94b5c6744a --- /dev/null +++ b/src/plugins/index_pattern_editor/public/lib/extract_time_fields.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { IndexPatternField } from '../../../../plugins/data/public'; +import { TimestampOption } from '../types'; + +export function extractTimeFields( + fields: IndexPatternField[], + requireTimestampField: boolean = false +): TimestampOption[] { + const dateFields = fields.filter((field) => field.type === 'date'); + + if (dateFields.length === 0) { + return []; + } + + const noTimeFieldLabel = i18n.translate( + 'indexPatternEditor.createIndexPattern.stepTime.noTimeFieldOptionLabel', + { + defaultMessage: "--- I don't want to use the time filter ---", + } + ); + const noTimeFieldOption = { + display: noTimeFieldLabel, + fieldName: '', + }; + + const timeFields = dateFields.map((field) => ({ + display: field.name, + fieldName: field.name, + })); + + if (!requireTimestampField) { + timeFields.push(noTimeFieldOption); + } + + return timeFields; +} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts b/src/plugins/index_pattern_editor/public/lib/get_indices.test.ts similarity index 79% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts rename to src/plugins/index_pattern_editor/public/lib/get_indices.test.ts index 29330cefc4806..fc96482f0379f 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts +++ b/src/plugins/index_pattern_editor/public/lib/get_indices.test.ts @@ -7,7 +7,7 @@ */ import { getIndices, responseToItemArray } from './get_indices'; -import { httpServiceMock } from '../../../../../../core/public/mocks'; +import { httpServiceMock } from '../../../../core/public/mocks'; import { ResolveIndexResponseItemIndexAttrs } from '../types'; export const successfulResponse = { @@ -33,26 +33,27 @@ export const successfulResponse = { }; const mockGetTags = () => []; +const mockIsRollupIndex = () => false; const http = httpServiceMock.createStartContract(); http.get.mockResolvedValue(successfulResponse); describe('getIndices', () => { it('should work in a basic case', async () => { - const result = await getIndices(http, mockGetTags, 'kibana', false); + const result = await getIndices(http, mockIsRollupIndex, 'kibana', false); expect(result.length).toBe(3); expect(result[0].name).toBe('f-alias'); expect(result[1].name).toBe('foo'); }); it('should ignore ccs query-all', async () => { - expect((await getIndices(http, mockGetTags, '*:', false)).length).toBe(0); + expect((await getIndices(http, mockIsRollupIndex, '*:', false)).length).toBe(0); }); it('should ignore a single comma', async () => { - expect((await getIndices(http, mockGetTags, ',', false)).length).toBe(0); - expect((await getIndices(http, mockGetTags, ',*', false)).length).toBe(0); - expect((await getIndices(http, mockGetTags, ',foobar', false)).length).toBe(0); + expect((await getIndices(http, mockIsRollupIndex, ',', false)).length).toBe(0); + expect((await getIndices(http, mockIsRollupIndex, ',*', false)).length).toBe(0); + expect((await getIndices(http, mockIsRollupIndex, ',foobar', false)).length).toBe(0); }); it('response object to item array', () => { @@ -89,7 +90,7 @@ describe('getIndices', () => { http.get.mockImplementationOnce(() => { throw new Error('Test error'); }); - const result = await getIndices(http, mockGetTags, 'kibana', false); + const result = await getIndices(http, mockIsRollupIndex, 'kibana', false); expect(result.length).toBe(0); }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts b/src/plugins/index_pattern_editor/public/lib/get_indices.ts similarity index 73% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts rename to src/plugins/index_pattern_editor/public/lib/get_indices.ts index e7edd177c2bda..625e99ecbcdc5 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts +++ b/src/plugins/index_pattern_editor/public/lib/get_indices.ts @@ -9,25 +9,41 @@ import { sortBy } from 'lodash'; import { HttpStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; -import { IndexPatternCreationConfig } from '../../../../../index_pattern_management/public'; +import { Tag, INDEX_PATTERN_TYPE } from '../types'; +// todo move into this plugin, consider removing all ipm references import { MatchedItem, ResolveIndexResponse, ResolveIndexResponseItemIndexAttrs } from '../types'; -const aliasLabel = i18n.translate('indexPatternManagement.aliasLabel', { defaultMessage: 'Alias' }); -const dataStreamLabel = i18n.translate('indexPatternManagement.dataStreamLabel', { +const aliasLabel = i18n.translate('indexPatternEditor.aliasLabel', { defaultMessage: 'Alias' }); +const dataStreamLabel = i18n.translate('indexPatternEditor.dataStreamLabel', { defaultMessage: 'Data stream', }); -const indexLabel = i18n.translate('indexPatternManagement.indexLabel', { +const indexLabel = i18n.translate('indexPatternEditor.indexLabel', { defaultMessage: 'Index', }); -const frozenLabel = i18n.translate('indexPatternManagement.frozenLabel', { +const frozenLabel = i18n.translate('indexPatternEditor.frozenLabel', { defaultMessage: 'Frozen', }); +const rollupLabel = i18n.translate('indexPatternEditor.rollupLabel', { + defaultMessage: 'Rollup', +}); + +const getIndexTags = (isRollupIndex: (indexName: string) => boolean) => (indexName: string) => + isRollupIndex(indexName) + ? [ + { + key: INDEX_PATTERN_TYPE.ROLLUP, + name: rollupLabel, + color: 'primary', + }, + ] + : []; + export async function getIndices( http: HttpStart, - getIndexTags: IndexPatternCreationConfig['getIndexTags'], + isRollupIndex: (indexName: string) => boolean, rawPattern: string, showAllIndices: boolean ): Promise { @@ -62,7 +78,7 @@ export async function getIndices( return []; } - return responseToItemArray(response, getIndexTags); + return responseToItemArray(response, getIndexTags(isRollupIndex)); } catch { return []; } @@ -70,7 +86,7 @@ export async function getIndices( export const responseToItemArray = ( response: ResolveIndexResponse, - getIndexTags: IndexPatternCreationConfig['getIndexTags'] + getTags: (indexName: string) => Tag[] ): MatchedItem[] => { const source: MatchedItem[] = []; @@ -78,7 +94,7 @@ export const responseToItemArray = ( const tags: MatchedItem['tags'] = [{ key: 'index', name: indexLabel, color: 'default' }]; const isFrozen = (index.attributes || []).includes(ResolveIndexResponseItemIndexAttrs.FROZEN); - tags.push(...getIndexTags(index.name)); + tags.push(...getTags(index.name)); if (isFrozen) { tags.push({ name: frozenLabel, key: 'frozen', color: 'danger' }); } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.test.ts b/src/plugins/index_pattern_editor/public/lib/get_matched_indices.test.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.test.ts rename to src/plugins/index_pattern_editor/public/lib/get_matched_indices.test.ts diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts b/src/plugins/index_pattern_editor/public/lib/get_matched_indices.ts similarity index 97% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts rename to src/plugins/index_pattern_editor/public/lib/get_matched_indices.ts index e9b365474425b..0b659aa5fbc76 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts +++ b/src/plugins/index_pattern_editor/public/lib/get_matched_indices.ts @@ -23,10 +23,6 @@ function isSystemIndex(index: string): boolean { } function filterSystemIndices(indices: MatchedItem[], isIncludingSystemIndices: boolean) { - if (!indices) { - return indices; - } - const acceptableIndices = isIncludingSystemIndices ? indices : // All system indices begin with a period. @@ -54,14 +50,14 @@ function filterSystemIndices(indices: MatchedItem[], isIncludingSystemIndices: b We call this `exact` matches because ES is telling us exactly what it matches */ -import { MatchedItem } from '../types'; +import { MatchedItem, MatchedIndicesSet } from '../types'; export function getMatchedIndices( unfilteredAllIndices: MatchedItem[], unfilteredPartialMatchedIndices: MatchedItem[], unfilteredExactMatchedIndices: MatchedItem[], isIncludingSystemIndices: boolean = false -) { +): MatchedIndicesSet { const allIndices = filterSystemIndices(unfilteredAllIndices, isIncludingSystemIndices); const partialMatchedIndices = filterSystemIndices( unfilteredPartialMatchedIndices, diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/index.ts b/src/plugins/index_pattern_editor/public/lib/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/index.ts rename to src/plugins/index_pattern_editor/public/lib/index.ts diff --git a/src/plugins/index_pattern_editor/public/mocks.ts b/src/plugins/index_pattern_editor/public/mocks.ts new file mode 100644 index 0000000000000..c7ac1d61e3c10 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/mocks.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IndexPatternEditorPlugin } from './plugin'; + +export type Start = jest.Mocked>; + +export type Setup = jest.Mocked>; + +const createSetupContract = (): Setup => { + return {}; +}; + +const createStartContract = (): Start => { + return { + openEditor: jest.fn(), + IndexPatternEditorComponent: jest.fn(), + userPermissions: { + editIndexPattern: jest.fn(), + }, + }; +}; + +export const indexPatternEditorPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/src/plugins/index_pattern_editor/public/open_editor.tsx b/src/plugins/index_pattern_editor/public/open_editor.tsx new file mode 100644 index 0000000000000..ec62a1d6ec7c6 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/open_editor.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { CoreStart, OverlayRef } from 'src/core/public'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { + createKibanaReactContext, + toMountPoint, + IndexPattern, + DataPublicPluginStart, +} from './shared_imports'; + +import { CloseEditor, IndexPatternEditorContext, IndexPatternEditorProps } from './types'; +import { IndexPatternEditorLazy } from './components/index_pattern_editor_lazy'; + +interface Dependencies { + core: CoreStart; + indexPatternService: DataPublicPluginStart['indexPatterns']; +} + +export const getEditorOpener = ({ core, indexPatternService }: Dependencies) => ( + options: IndexPatternEditorProps +): CloseEditor => { + const { uiSettings, overlays, docLinks, notifications, http, application } = core; + const { + Provider: KibanaReactContextProvider, + } = createKibanaReactContext({ + uiSettings, + docLinks, + http, + notifications, + application, + indexPatternService, + }); + + let overlayRef: OverlayRef | null = null; + + const openEditor = ({ + onSave, + onCancel = () => {}, + defaultTypeIsRollup = false, + requireTimestampField = false, + }: IndexPatternEditorProps): CloseEditor => { + const closeEditor = () => { + if (overlayRef) { + overlayRef.close(); + overlayRef = null; + } + }; + + const onSaveIndexPattern = (indexPattern: IndexPattern) => { + closeEditor(); + + if (onSave) { + onSave(indexPattern); + } + }; + + overlayRef = overlays.openFlyout( + toMountPoint( + + + { + closeEditor(); + onCancel(); + }} + defaultTypeIsRollup={defaultTypeIsRollup} + requireTimestampField={requireTimestampField} + /> + + + ), + { + hideCloseButton: true, + size: 'l', + } + ); + + return closeEditor; + }; + + return openEditor(options); +}; diff --git a/src/plugins/index_pattern_editor/public/plugin.test.tsx b/src/plugins/index_pattern_editor/public/plugin.test.tsx new file mode 100644 index 0000000000000..b11d6b0c176b6 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/plugin.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; + +jest.mock('../../kibana_react/public', () => { + const original = jest.requireActual('../../kibana_react/public'); + + return { + ...original, + toMountPoint: (node: React.ReactNode) => node, + }; +}); + +import { CoreStart } from 'src/core/public'; +import { coreMock } from 'src/core/public/mocks'; +import { dataPluginMock } from '../../data/public/mocks'; +import { usageCollectionPluginMock } from '../../usage_collection/public/mocks'; + +import { IndexPatternEditorLazy } from './components/index_pattern_editor_lazy'; +import { IndexPatternEditorPlugin } from './plugin'; + +const noop = () => {}; + +describe('IndexPatternEditorPlugin', () => { + const coreStart: CoreStart = coreMock.createStart(); + const pluginStart = { + data: dataPluginMock.createStartContract(), + usageCollection: usageCollectionPluginMock.createSetupContract(), + }; + + let plugin: IndexPatternEditorPlugin; + + beforeEach(() => { + plugin = new IndexPatternEditorPlugin(); + }); + + test('should expose a handler to open the indexpattern field editor', async () => { + const startApi = await plugin.start(coreStart, pluginStart); + expect(startApi.openEditor).toBeDefined(); + }); + + test('should call core.overlays.openFlyout when opening the editor', async () => { + const openFlyout = jest.fn(); + const onSaveSpy = jest.fn(); + + const coreStartMocked = { + ...coreStart, + overlays: { + ...coreStart.overlays, + openFlyout, + }, + }; + const { openEditor } = await plugin.start(coreStartMocked, pluginStart); + + openEditor({ onSave: onSaveSpy }); + + expect(openFlyout).toHaveBeenCalled(); + + const [[arg]] = openFlyout.mock.calls; + const i18nProvider = arg.props.children; + expect(i18nProvider.props.children.type).toBe(IndexPatternEditorLazy); + + // We force call the "onSave" prop from the component + // and make sure that the the spy is being called. + // Note: we are testing implementation details, if we change or rename the "onSave" prop on + // the component, we will need to update this test accordingly. + expect(i18nProvider.props.children.props.onSave).toBeDefined(); + i18nProvider.props.children.props.onSave(); + expect(onSaveSpy).toHaveBeenCalled(); + }); + + test('should return a handler to close the flyout', async () => { + const { openEditor } = await plugin.start(coreStart, pluginStart); + + const closeEditorHandler = openEditor({ onSave: noop }); + expect(typeof closeEditorHandler).toBe('function'); + }); +}); diff --git a/src/plugins/index_pattern_editor/public/plugin.tsx b/src/plugins/index_pattern_editor/public/plugin.tsx new file mode 100644 index 0000000000000..ca72249496e77 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/plugin.tsx @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { Plugin, CoreSetup, CoreStart } from 'src/core/public'; + +import { + PluginSetup, + PluginStart, + SetupPlugins, + StartPlugins, + IndexPatternEditorProps, +} from './types'; +import { getEditorOpener } from './open_editor'; +import { IndexPatternEditor } from './components/index_pattern_editor'; + +export class IndexPatternEditorPlugin + implements Plugin { + public setup(core: CoreSetup, plugins: SetupPlugins): PluginSetup { + return {}; + } + + public start(core: CoreStart, plugins: StartPlugins) { + const { application, uiSettings, docLinks, http, notifications } = core; + const { data } = plugins; + + return { + /** + * Index pattern editor flyout via function interface + * @param IndexPatternEditorProps - index pattern editor config + * @returns method to close editor + */ + openEditor: getEditorOpener({ + core, + indexPatternService: data.indexPatterns, + }), + /** + * Index pattern editor flyout via react component + * @param IndexPatternEditorProps - index pattern editor config + * @returns JSX.Element + */ + IndexPatternEditorComponent: (props: IndexPatternEditorProps) => ( + + ), + /** + * Convenience method to determine whether the user can create or edit edit the index patterns. + * + * @returns boolean + */ + userPermissions: { + editIndexPattern: () => { + return application.capabilities.management.kibana.indexPatterns; + }, + }, + }; + } + + public stop() { + return {}; + } +} diff --git a/src/plugins/index_pattern_editor/public/shared_imports.ts b/src/plugins/index_pattern_editor/public/shared_imports.ts new file mode 100644 index 0000000000000..c99d32e0c8a28 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/shared_imports.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + IndexPattern, + IndexPatternField, + DataPublicPluginStart, + IndexPatternSpec, + GetFieldsOptions, + IndexPatternAggRestrictions, +} from '../../data/public'; + +export { + createKibanaReactContext, + toMountPoint, + CodeEditor, + useKibana, +} from '../../kibana_react/public'; + +export { + useForm, + useFormData, + useFormContext, + Form, + FormSchema, + UseField, + FormHook, + ValidationFunc, + FieldConfig, + getFieldValidityAndErrorMessage, + ValidationConfig, +} from '../../es_ui_shared/static/forms/hook_form_lib'; + +export { fieldValidators } from '../../es_ui_shared/static/forms/helpers'; + +export { + TextField, + ToggleField, + NumericField, + SelectField, + FormRow, + SuperSelectField, +} from '../../es_ui_shared/static/forms/components'; + +export { HttpStart } from '../../../core/public'; diff --git a/src/plugins/index_pattern_editor/public/test_utils/helpers.ts b/src/plugins/index_pattern_editor/public/test_utils/helpers.ts new file mode 100644 index 0000000000000..b55a59df34545 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/test_utils/helpers.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { act } from 'react-dom/test-utils'; +import { TestBed } from './test_utils'; + +export const getCommonActions = (testBed: TestBed) => { + const toggleFormRow = (row: 'customLabel' | 'value' | 'format', value: 'on' | 'off' = 'on') => { + const testSubj = `${row}Row.toggle`; + const toggle = testBed.find(testSubj); + const isOn = toggle.props()['aria-checked']; + + if ((value === 'on' && isOn) || (value === 'off' && isOn === false)) { + return; + } + + testBed.form.toggleEuiSwitch(testSubj); + }; + + const changeFieldType = async (value: string, label?: string) => { + await act(async () => { + testBed.find('typeField').simulate('change', [ + { + value, + label: label ?? value, + }, + ]); + }); + testBed.component.update(); + }; + + return { + toggleFormRow, + changeFieldType, + }; +}; diff --git a/src/plugins/index_pattern_editor/public/test_utils/index.ts b/src/plugins/index_pattern_editor/public/test_utils/index.ts new file mode 100644 index 0000000000000..b5d943281cd79 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/test_utils/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './test_utils'; + +export * from './mocks'; + +export * from './helpers'; diff --git a/src/plugins/index_pattern_editor/public/test_utils/mocks.ts b/src/plugins/index_pattern_editor/public/test_utils/mocks.ts new file mode 100644 index 0000000000000..5d0296a04ea51 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/test_utils/mocks.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DocLinksStart } from 'src/core/public'; + +export const noop = () => {}; + +export const docLinks: DocLinksStart = { + ELASTIC_WEBSITE_URL: 'htts://jestTest.elastic.co', + DOC_LINK_VERSION: 'jest', + links: {} as any, +}; diff --git a/src/plugins/index_pattern_editor/public/test_utils/test_utils.ts b/src/plugins/index_pattern_editor/public/test_utils/test_utils.ts new file mode 100644 index 0000000000000..c8e4aedc26471 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/test_utils/test_utils.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getRandomString } from '@kbn/test/jest'; + +export { registerTestBed, TestBed } from '@kbn/test/jest'; diff --git a/src/plugins/index_pattern_editor/public/types.ts b/src/plugins/index_pattern_editor/public/types.ts new file mode 100644 index 0000000000000..2a2abe249b330 --- /dev/null +++ b/src/plugins/index_pattern_editor/public/types.ts @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FC } from 'react'; +import { + ApplicationStart, + IUiSettingsClient, + NotificationsStart, + DocLinksStart, + HttpSetup, +} from 'src/core/public'; + +import { EuiComboBoxOptionOption } from '@elastic/eui'; + +import type { IndexPattern } from 'src/plugins/data/public'; +import { DataPublicPluginStart, IndexPatternAggRestrictions } from './shared_imports'; + +export interface IndexPatternEditorContext { + uiSettings: IUiSettingsClient; + docLinks: DocLinksStart; + http: HttpSetup; + notifications: NotificationsStart; + application: ApplicationStart; + indexPatternService: DataPublicPluginStart['indexPatterns']; +} + +/** @public */ +export interface IndexPatternEditorProps { + /** + * Handler for the "save" footer button + * @param indexPattern - newly created index pattern + */ + onSave: (indexPattern: IndexPattern) => void; + /** + * Handler for the "cancel" footer button + */ + onCancel?: () => void; + /** + * Sets the default index pattern type to rollup. Defaults to false. + */ + defaultTypeIsRollup?: boolean; + /** + * Sets whether a timestamp field is required to create an index pattern. Defaults to false. + */ + requireTimestampField?: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PluginSetup {} + +export interface PluginStart { + openEditor(options: IndexPatternEditorProps): () => void; + IndexPatternEditorComponent: FC; + userPermissions: { + editIndexPattern: () => boolean; + }; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SetupPlugins {} + +export interface StartPlugins { + data: DataPublicPluginStart; +} + +export type CloseEditor = () => void; + +export interface MatchedItem { + name: string; + tags: Tag[]; + item: { + name: string; + backing_indices?: string[]; + timestamp_field?: string; + indices?: string[]; + aliases?: string[]; + attributes?: ResolveIndexResponseItemIndexAttrs[]; + data_stream?: string; + }; +} + +// for showing index matches +export interface ResolveIndexResponse { + indices?: ResolveIndexResponseItemIndex[]; + aliases?: ResolveIndexResponseItemAlias[]; + data_streams?: ResolveIndexResponseItemDataStream[]; +} + +export interface ResolveIndexResponseItem { + name: string; +} + +export interface ResolveIndexResponseItemDataStream extends ResolveIndexResponseItem { + backing_indices: string[]; + timestamp_field: string; +} + +export interface ResolveIndexResponseItemAlias extends ResolveIndexResponseItem { + indices: string[]; +} + +export interface ResolveIndexResponseItemIndex extends ResolveIndexResponseItem { + aliases?: string[]; + attributes?: ResolveIndexResponseItemIndexAttrs[]; + data_stream?: string; +} + +export interface Tag { + name: string; + key: string; + color: string; +} +// end for index matches + +export interface IndexPatternTableItem { + id: string; + title: string; + default: boolean; + tag?: string[]; + sort: string; +} + +// copied from index pattern management, needs review +export interface MatchedItem { + name: string; + tags: Tag[]; + item: { + name: string; + backing_indices?: string[]; + timestamp_field?: string; + indices?: string[]; + aliases?: string[]; + attributes?: ResolveIndexResponseItemIndexAttrs[]; + data_stream?: string; + }; +} + +export enum ResolveIndexResponseItemIndexAttrs { + OPEN = 'open', + CLOSED = 'closed', + HIDDEN = 'hidden', + FROZEN = 'frozen', +} + +export interface RollupIndiciesCapability { + aggs: Record; + error: string; +} + +export type RollupIndicesCapsResponse = Record; + +export enum INDEX_PATTERN_TYPE { + ROLLUP = 'rollup', + DEFAULT = 'default', +} + +export interface IndexPatternConfig { + title: string; + timestampField?: EuiComboBoxOptionOption; + allowHidden: boolean; + id?: string; + type: INDEX_PATTERN_TYPE; +} + +export interface FormInternal extends Omit { + timestampField?: TimestampOption; +} + +export interface TimestampOption { + display: string; + fieldName?: string; +} + +export interface MatchedIndicesSet { + allIndices: MatchedItem[]; + exactMatchedIndices: MatchedItem[]; + partialMatchedIndices: MatchedItem[]; + visibleIndices: MatchedItem[]; +} diff --git a/src/plugins/index_pattern_editor/tsconfig.json b/src/plugins/index_pattern_editor/tsconfig.json new file mode 100644 index 0000000000000..559b1aaf0fc26 --- /dev/null +++ b/src/plugins/index_pattern_editor/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "public/**/*", + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../data/tsconfig.json" }, + { "path": "../kibana_react/tsconfig.json" }, + { "path": "../kibana_utils/tsconfig.json" }, + { "path": "../es_ui_shared/tsconfig.json" }, + ] +} diff --git a/src/plugins/index_pattern_management/kibana.json b/src/plugins/index_pattern_management/kibana.json index 7d9d885bbf9d6..3ba56eda3620e 100644 --- a/src/plugins/index_pattern_management/kibana.json +++ b/src/plugins/index_pattern_management/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["management", "data", "urlForwarding", "indexPatternFieldEditor"], + "requiredPlugins": ["management", "data", "urlForwarding", "indexPatternFieldEditor", "indexPatternEditor"], "requiredBundles": ["kibanaReact", "kibanaUtils"], "owner": { "name": "App Services", diff --git a/src/plugins/index_pattern_management/public/components/__snapshots__/utils.test.ts.snap b/src/plugins/index_pattern_management/public/components/__snapshots__/utils.test.ts.snap index 224a5c992d58b..3a25a78472b50 100644 --- a/src/plugins/index_pattern_management/public/components/__snapshots__/utils.test.ts.snap +++ b/src/plugins/index_pattern_management/public/components/__snapshots__/utils.test.ts.snap @@ -6,14 +6,19 @@ Array [ "default": true, "id": "test", "sort": "0test name", - "tags": undefined, + "tags": Array [ + Object { + "key": "default", + "name": "Default", + }, + ], "title": "test name", }, Object { "default": false, "id": "test1", "sort": "1test name 1", - "tags": undefined, + "tags": Array [], "title": "test name 1", }, ] diff --git a/src/plugins/index_pattern_management/public/components/create_button/create_button.tsx b/src/plugins/index_pattern_management/public/components/create_button/create_button.tsx deleted file mode 100644 index 0da250f5cba3b..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_button/create_button.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// @ts-ignore -import { euiColorAccent } from '@elastic/eui/dist/eui_theme_light.json'; -import React, { Component, Fragment } from 'react'; - -import { - EuiBadge, - EuiButton, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiDescriptionList, - EuiDescriptionListDescription, - EuiDescriptionListTitle, - EuiPopover, -} from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -interface State { - isPopoverOpen: boolean; -} - -interface Props { - options: Array<{ - text: string; - description?: string; - testSubj?: string; - isBeta?: boolean; - onClick: () => void; - }>; -} - -export class CreateButton extends Component { - public state = { - isPopoverOpen: false, - }; - - public render() { - const { options, children } = this.props; - const { isPopoverOpen } = this.state; - - if (!options || !options.length) { - return null; - } - - if (options.length === 1) { - return ( - - {children} - - ); - } - - const button = ( - - {children} - - ); - - if (options.length > 1) { - return ( - - { - return ( - - - - {option.text} - {option.isBeta ? {this.renderBetaBadge()} : null} - - - {option.description} - - - - ); - })} - /> - - ); - } - } - - private togglePopover = () => { - this.setState({ - isPopoverOpen: !this.state.isPopoverOpen, - }); - }; - - private closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - }; - - private renderBetaBadge = () => { - return ( - - - - ); - }; -} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/CREATE_INDEX_PATTERN.md b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/CREATE_INDEX_PATTERN.md deleted file mode 100644 index 6e01bf001691e..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/CREATE_INDEX_PATTERN.md +++ /dev/null @@ -1,15 +0,0 @@ -# Create Index Pattern - -This is meant to serve as a guide to this area of code. - -## Bye bye regressions -In order to prevent future regressions, there are a few scenarios -that need to be tested with each change to this area of the code. - -- Cross-cluster search - - Ensure changes work properly in a CCS environment - - A solid CCS environment involves various indices on all nodes including the controlling node. -- Alias support - - Indices are the most common use case, but we also support aliases. -- Time field name - - This needs to be `undefined` if the user does not select one (other areas of Kibana depend on this) \ No newline at end of file diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap deleted file mode 100644 index 38a9e47014416..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap +++ /dev/null @@ -1,250 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CreateIndexPatternWizard defaults to the loading state 1`] = ` - - - - -`; - -exports[`CreateIndexPatternWizard renders index pattern step when there are indices 1`] = ` - -
- - - - -`; - -exports[`CreateIndexPatternWizard renders the empty state when there are no indices 1`] = ` - -
- - - - -`; - -exports[`CreateIndexPatternWizard renders time field step when step is set to 2 1`] = ` - -
- - - - -`; - -exports[`CreateIndexPatternWizard renders when there are no indices but there are remote clusters 1`] = ` - -
- - - - -`; - -exports[`CreateIndexPatternWizard shows system indices even if there are no other indices if the include system indices is toggled 1`] = ` - -
- - - - -`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap deleted file mode 100644 index 5e5fbb7c5e99d..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap +++ /dev/null @@ -1,499 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Header should render a different name, prompt, and beta tag if provided 1`] = ` -
- Test prompt -
- } -> - - Create test index pattern - - - - - - } - > -
- - Create test index pattern - - - - - - } - responsive={true} - > -
- -
- -
- -

- Create test index pattern - - - - Beta - - -

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

- - multiple - , - "single": - filebeat-4-3-22 - , - "star": - filebeat-* - , - } - } - > - - An index pattern can match a single source, for example, - - - - - filebeat-4-3-22 - - - - - , or - - multiple - - data sources, - - - - - filebeat-* - - - - - . - - -
- - - -

-
-
- -
- -
- Test prompt -
-
-
- -
-
- -`; - -exports[`Header should render normally 1`] = ` -
- - Create test index pattern - - } - > -
- - Create test index pattern - - } - responsive={true} - > -
- -
- -
- -

- Create test index pattern -

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

- - multiple - , - "single": - filebeat-4-3-22 - , - "star": - filebeat-* - , - } - } - > - - An index pattern can match a single source, for example, - - - - - filebeat-4-3-22 - - - - - , or - - multiple - - data sources, - - - - - filebeat-* - - - - - . - - -
- - - -

-
-
-
-
- -
-
-
-`; - -exports[`Header should render without including system indices 1`] = ` -
- - Create test index pattern - - } - > -
- - Create test index pattern - - } - responsive={true} - > -
- -
- -
- -

- Create test index pattern -

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

- - multiple - , - "single": - filebeat-4-3-22 - , - "star": - filebeat-* - , - } - } - > - - An index pattern can match a single source, for example, - - - - - filebeat-4-3-22 - - - - - , or - - multiple - - data sources, - - - - - filebeat-* - - - - - . - - -
- - - -

-
-
-
-
- -
-
-
-`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx deleted file mode 100644 index 188fe2fbc02af..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { Header } from '../header'; -import { mount } from 'enzyme'; -import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; -import { mockManagementPlugin } from '../../../../mocks'; -import { DocLinksStart } from 'kibana/public'; - -describe('Header', () => { - const indexPatternName = 'test index pattern'; - const mockedContext = mockManagementPlugin.createIndexPatternManagmentContext(); - const mockedDocLinks = { - links: { - indexPatterns: {}, - }, - } as DocLinksStart; - - it('should render normally', () => { - const component = mount( -
, - { - wrappingComponent: KibanaContextProvider, - wrappingComponentProps: { - services: mockedContext, - }, - } - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render without including system indices', () => { - const component = mount( -
, - { - wrappingComponent: KibanaContextProvider, - wrappingComponentProps: { - services: mockedContext, - }, - } - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render a different name, prompt, and beta tag if provided', () => { - const component = mount( -
Test prompt} - indexPatternName={indexPatternName} - isBeta={true} - docLinks={mockedDocLinks} - />, - { - wrappingComponent: KibanaContextProvider, - wrappingComponentProps: { - services: mockedContext, - }, - } - ); - - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx deleted file mode 100644 index c708bd3cac33e..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; - -import { EuiBetaBadge, EuiCode, EuiLink, EuiPageHeader, EuiSpacer, EuiText } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { DocLinksStart } from 'kibana/public'; -import { useKibana } from '../../../../../../../plugins/kibana_react/public'; -import { IndexPatternManagmentContext } from '../../../../types'; - -export const Header = ({ - prompt, - indexPatternName, - isBeta = false, - docLinks, -}: { - prompt?: React.ReactNode; - indexPatternName: string; - isBeta?: boolean; - docLinks: DocLinksStart; -}) => { - const changeTitle = useKibana().services.chrome.docTitle.change; - const createIndexPatternHeader = i18n.translate( - 'indexPatternManagement.createIndexPatternHeader', - { - defaultMessage: 'Create {indexPatternName}', - values: { indexPatternName }, - } - ); - - changeTitle(createIndexPatternHeader); - - return ( - - {createIndexPatternHeader} - {isBeta ? ( - <> - {' '} - - - ) : null} - - } - bottomBorder - > - -

- multiple, - single: filebeat-4-3-22, - star: filebeat-*, - }} - /> -
- - - -

-
- {prompt ? ( - <> - - {prompt} - - ) : null} -
- ); -}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/__snapshots__/loading_state.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/__snapshots__/loading_state.test.tsx.snap deleted file mode 100644 index 1e6ac56d437e1..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/__snapshots__/loading_state.test.tsx.snap +++ /dev/null @@ -1,39 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LoadingState should render normally 1`] = ` - - - -

- -

-
-
- - - -
-`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/loading_state.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/loading_state.tsx deleted file mode 100644 index 9cfb9321b3f00..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/loading_state/loading_state.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; - -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiTitle } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -export const LoadingState = () => ( - - - -

- -

-
-
- - - - -
-); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/__snapshots__/step_index_pattern.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/__snapshots__/step_index_pattern.test.tsx.snap deleted file mode 100644 index 813a0c61c0829..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/__snapshots__/step_index_pattern.test.tsx.snap +++ /dev/null @@ -1,58 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`StepIndexPattern renders errors when input is invalid 1`] = ` -Object { - "component":
, |" - data-test-subj="createIndexPatternStep1Header" - errors={ - Array [ - "A name cannot contain spaces or the characters: \\\\, /, ?, \\", <, >, |", - ] - } - goToNextStep={[Function]} - isIncludingSystemIndices={false} - isInputInvalid={true} - isNextStepDisabled={true} - onChangeIncludingSystemIndices={[Function]} - onQueryChanged={[Function]} - query="?" - />, -} -`; - -exports[`StepIndexPattern renders indices which match the initial query 1`] = ` - -`; - -exports[`StepIndexPattern renders matching indices when input is valid 1`] = ` - -`; - -exports[`StepIndexPattern renders the loading state 1`] = ` - -`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/__snapshots__/header.test.tsx.snap deleted file mode 100644 index 851e5cc4c2a76..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/__snapshots__/header.test.tsx.snap +++ /dev/null @@ -1,221 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Header should mark the input as invalid 1`] = ` -
- -

- -

-
- - - - - - - * - , - } - } - /> - - - % - , - } - } - /> - - } - isInvalid={true} - label={ - - } - labelType="label" - > - - - - - - - - - - - - -
-`; - -exports[`Header should render normally 1`] = ` -
- -

- -

-
- - - - - - - * - , - } - } - /> - - - % - , - } - } - /> - - } - isInvalid={false} - label={ - - } - labelType="label" - > - - - - - - - - - - - - -
-`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.test.tsx deleted file mode 100644 index 8c7e43ed57c77..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.test.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { Header } from '../header'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; - -describe('Header', () => { - it('should render normally', () => { - const component = shallowWithI18nProvider( -
{}} - goToNextStep={() => {}} - isNextStepDisabled={false} - onChangeIncludingSystemIndices={() => {}} - isIncludingSystemIndices={false} - /> - ); - - expect(component).toMatchSnapshot(); - }); - - it('should mark the input as invalid', () => { - const component = shallowWithI18nProvider( -
{}} - goToNextStep={() => {}} - isNextStepDisabled={true} - onChangeIncludingSystemIndices={() => {}} - isIncludingSystemIndices={false} - /> - ); - - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx deleted file mode 100644 index 342665b185e79..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; - -import { - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiButton, - EuiForm, - EuiFormRow, - EuiFieldText, - EuiSwitchEvent, - EuiSwitch, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -interface HeaderProps { - isInputInvalid: boolean; - errors: any; - characterList: string; - query: string; - onQueryChanged: (e: React.ChangeEvent) => void; - goToNextStep: (query: string) => void; - isNextStepDisabled: boolean; - showSystemIndices?: boolean; - onChangeIncludingSystemIndices: (event: EuiSwitchEvent) => void; - isIncludingSystemIndices: boolean; -} - -export const Header: React.FC = ({ - isInputInvalid, - errors, - characterList, - query, - onQueryChanged, - goToNextStep, - isNextStepDisabled, - showSystemIndices = false, - onChangeIncludingSystemIndices, - isIncludingSystemIndices, - ...rest -}) => ( -
- -

- -

-
- - - - - - } - isInvalid={isInputInvalid} - error={errors} - helpText={ - <> - * }} - />{' '} - {characterList} }} - /> - - } - > - - - - {showSystemIndices ? ( - - - } - id="checkboxShowSystemIndices" - checked={isIncludingSystemIndices} - onChange={onChangeIncludingSystemIndices} - data-test-subj="showSystemAndHiddenIndices" - /> - - ) : null} - - - - - goToNextStep(query)} - isDisabled={isNextStepDisabled} - data-test-subj="createIndexPatternGoToStep2Button" - > - - - - - -
-); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/index.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/index.ts deleted file mode 100644 index 3d87a6d7c39dc..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { StepIndexPattern } from './step_index_pattern'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx deleted file mode 100644 index 8b4f751a4e3a3..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { StepIndexPattern, canPreselectTimeField } from './step_index_pattern'; -import { Header } from './components/header'; -import { IndexPatternCreationConfig } from '../../../../../../../plugins/index_pattern_management/public'; -import { mockManagementPlugin } from '../../../../mocks'; -import { createComponentWithContext } from '../../../test_utils'; - -jest.mock('../../lib/ensure_minimum_time', () => ({ - ensureMinimumTime: async (promises: Array>) => - Array.isArray(promises) ? await Promise.all(promises) : await promises, -})); - -const mockIndexPatternCreationType = new IndexPatternCreationConfig({ - type: 'default', - name: 'name', -}); - -jest.mock('../../lib/get_indices', () => ({ - getIndices: ({}, {}, query: string) => { - if (query.startsWith('e')) { - return [{ name: 'es', item: {} }]; - } - - return [{ name: 'kibana', item: {} }]; - }, -})); - -const allIndices = [ - { name: 'kibana', tags: [], item: {} }, - { name: 'es', tags: [], item: {} }, -]; - -const goToNextStep = () => {}; - -const mockContext = mockManagementPlugin.createIndexPatternManagmentContext(); - -mockContext.data.indexPatterns.getTitles = async () => Promise.resolve([]); -mockContext.uiSettings.get.mockReturnValue(''); - -describe('StepIndexPattern', () => { - it('renders the loading state', () => { - const component = createComponentWithContext( - StepIndexPattern, - { - allIndices, - isIncludingSystemIndices: false, - goToNextStep, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - component.setState({ isLoadingIndices: true }); - expect(component.find('[data-test-subj="createIndexPatternStep1Loading"]')).toMatchSnapshot(); - }); - - it('renders indices which match the initial query', async () => { - const component = createComponentWithContext( - StepIndexPattern, - { - allIndices, - isIncludingSystemIndices: false, - goToNextStep, - indexPatternCreationType: mockIndexPatternCreationType, - initialQuery: 'kibana', - }, - mockContext - ); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - await component.update(); - - expect( - component.find('[data-test-subj="createIndexPatternStep1IndicesList"]') - ).toMatchSnapshot(); - }); - - it('renders errors when input is invalid', async () => { - const component = createComponentWithContext( - StepIndexPattern, - { - allIndices, - isIncludingSystemIndices: false, - goToNextStep, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - const instance = component.instance() as StepIndexPattern; - instance.onQueryChanged({ target: { value: '?' } } as React.ChangeEvent); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - expect({ - component: component.find('[data-test-subj="createIndexPatternStep1Header"]'), - }).toMatchSnapshot(); - }); - - it('renders matching indices when input is valid', async () => { - const component = createComponentWithContext( - StepIndexPattern, - { - allIndices, - isIncludingSystemIndices: false, - goToNextStep, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - const instance = component.instance() as StepIndexPattern; - instance.onQueryChanged({ target: { value: 'k' } } as React.ChangeEvent); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - - expect( - component.find('[data-test-subj="createIndexPatternStep1IndicesList"]') - ).toMatchSnapshot(); - }); - - it('appends a wildcard automatically to queries', async () => { - const component = createComponentWithContext( - StepIndexPattern, - { - allIndices, - isIncludingSystemIndices: false, - goToNextStep, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - const instance = component.instance() as StepIndexPattern; - instance.onQueryChanged({ target: { value: 'k' } } as React.ChangeEvent); - expect(component.state('query')).toBe('k*'); - }); - - it('disables the next step if the index pattern exists', async () => { - const component = createComponentWithContext( - StepIndexPattern, - { - allIndices, - isIncludingSystemIndices: false, - goToNextStep, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - component.setState({ indexPatternExists: true }); - expect(component.find(Header).prop('isNextStepDisabled')).toBe(true); - }); - - it('ensures the response of the latest request is persisted', async () => { - const component = createComponentWithContext( - StepIndexPattern, - { - allIndices, - isIncludingSystemIndices: false, - goToNextStep, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - const instance = component.instance() as StepIndexPattern; - instance.onQueryChanged({ target: { value: 'e' } } as React.ChangeEvent); - instance.lastQuery = 'k'; - await new Promise((resolve) => process.nextTick(resolve)); - - // Honesty, the state would match the result of the `k` query but - // it's hard to mock this in tests but if remove our fix - // (the early return if the queries do not match) then this - // equals [{name: 'es'}] - expect(component.state('exactMatchedIndices')).toEqual([]); - - // Ensure it works in the other code flow too (the other early return) - - // Provide `es` so we do not auto append * and enter our other code flow - instance.onQueryChanged({ target: { value: 'es' } } as React.ChangeEvent); - instance.lastQuery = 'k'; - await new Promise((resolve) => process.nextTick(resolve)); - expect(component.state('exactMatchedIndices')).toEqual([]); - }); - - it('it can preselect time field', async () => { - const dataStream1 = { - name: 'data stream 1', - tags: [], - item: { name: 'data stream 1', backing_indices: [], timestamp_field: 'timestamp_field' }, - }; - - const dataStream2 = { - name: 'data stream 2', - tags: [], - item: { name: 'data stream 2', backing_indices: [], timestamp_field: 'timestamp_field' }, - }; - - const differentDataStream = { - name: 'different data stream', - tags: [], - item: { name: 'different data stream 2', backing_indices: [], timestamp_field: 'x' }, - }; - - const index = { - name: 'index', - tags: [], - item: { - name: 'index', - }, - }; - - const alias = { - name: 'alias', - tags: [], - item: { - name: 'alias', - indices: [], - }, - }; - - expect(canPreselectTimeField([index])).toEqual(undefined); - expect(canPreselectTimeField([alias])).toEqual(undefined); - expect(canPreselectTimeField([index, alias, dataStream1])).toEqual(undefined); - - expect(canPreselectTimeField([dataStream1])).toEqual('timestamp_field'); - - expect(canPreselectTimeField([dataStream1, dataStream2])).toEqual('timestamp_field'); - - expect(canPreselectTimeField([dataStream1, dataStream2, differentDataStream])).toEqual( - undefined - ); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx deleted file mode 100644 index 052e454041181..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { Component } from 'react'; -import { EuiSpacer, EuiCallOut, EuiSwitchEvent } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { indexPatterns, UI_SETTINGS } from '../../../../../../../plugins/data/public'; -import { - getIndices, - containsIllegalCharacters, - getMatchedIndices, - canAppendWildcard, - ensureMinimumTime, -} from '../../lib'; -import { LoadingIndices } from './components/loading_indices'; -import { StatusMessage } from './components/status_message'; -import { IndicesList } from './components/indices_list'; -import { Header } from './components/header'; -import { context as contextType } from '../../../../../../kibana_react/public'; -import { IndexPatternCreationConfig } from '../../../../../../../plugins/index_pattern_management/public'; -import { MatchedItem } from '../../types'; -import { IndexPatternManagmentContextValue } from '../../../../types'; - -interface StepIndexPatternProps { - allIndices: MatchedItem[]; - indexPatternCreationType: IndexPatternCreationConfig; - goToNextStep: (query: string, timestampField?: string) => void; - initialQuery?: string; - showSystemIndices: boolean; -} - -interface StepIndexPatternState { - partialMatchedIndices: MatchedItem[]; - exactMatchedIndices: MatchedItem[]; - isLoadingIndices: boolean; - existingIndexPatterns: string[]; - indexPatternExists: boolean; - query: string; - appendedWildcard: boolean; - showingIndexPatternQueryErrors: boolean; - indexPatternName: string; - isIncludingSystemIndices: boolean; -} - -export const canPreselectTimeField = (indices: MatchedItem[]) => { - const preselectStatus = indices.reduce( - ( - { canPreselect, timeFieldName }: { canPreselect: boolean; timeFieldName?: string }, - matchedItem - ) => { - const dataStreamItem = matchedItem.item; - const dataStreamTimestampField = dataStreamItem.timestamp_field; - const isDataStream = !!dataStreamItem.timestamp_field; - const timestampFieldMatches = - timeFieldName === undefined || timeFieldName === dataStreamTimestampField; - - return { - canPreselect: canPreselect && isDataStream && timestampFieldMatches, - timeFieldName: dataStreamTimestampField || timeFieldName, - }; - }, - { - canPreselect: true, - timeFieldName: undefined, - } - ); - - return preselectStatus.canPreselect ? preselectStatus.timeFieldName : undefined; -}; - -export class StepIndexPattern extends Component { - static contextType = contextType; - - public readonly context!: IndexPatternManagmentContextValue; - - state = { - partialMatchedIndices: [], - exactMatchedIndices: [], - isLoadingIndices: false, - existingIndexPatterns: [], - indexPatternExists: false, - query: '', - appendedWildcard: false, - showingIndexPatternQueryErrors: false, - indexPatternName: '', - isIncludingSystemIndices: false, - }; - - ILLEGAL_CHARACTERS = [...indexPatterns.ILLEGAL_CHARACTERS]; - - constructor(props: StepIndexPatternProps, context: IndexPatternManagmentContextValue) { - super(props, context); - const { indexPatternCreationType, initialQuery } = this.props; - - this.state.query = - initialQuery || context.services.uiSettings.get(UI_SETTINGS.INDEXPATTERN_PLACEHOLDER); - this.state.indexPatternName = indexPatternCreationType.getIndexPatternName(); - } - - lastQuery = ''; - - async UNSAFE_componentWillMount() { - this.fetchExistingIndexPatterns(); - if (this.state.query) { - this.lastQuery = this.state.query; - this.fetchIndices(this.state.query); - } - } - - fetchExistingIndexPatterns = async () => { - const existingIndexPatterns = await this.context.services.data.indexPatterns.getTitles(); - this.setState({ existingIndexPatterns }); - }; - - fetchIndices = async (query: string) => { - const { indexPatternCreationType } = this.props; - const { existingIndexPatterns } = this.state; - - if ((existingIndexPatterns as string[]).includes(query)) { - this.setState({ indexPatternExists: true }); - return; - } - - this.setState({ isLoadingIndices: true, indexPatternExists: false }); - - if (query.endsWith('*')) { - const exactMatchedIndices = await ensureMinimumTime( - getIndices( - this.context.services.http, - (indexName: string) => indexPatternCreationType.getIndexTags(indexName), - query, - this.state.isIncludingSystemIndices - ) - ); - // If the search changed, discard this state - if (query !== this.lastQuery) { - return; - } - this.setState({ exactMatchedIndices, isLoadingIndices: false }); - return; - } - - const [partialMatchedIndices, exactMatchedIndices] = await ensureMinimumTime([ - getIndices( - this.context.services.http, - (indexName: string) => indexPatternCreationType.getIndexTags(indexName), - `${query}*`, - this.state.isIncludingSystemIndices - ), - getIndices( - this.context.services.http, - (indexName: string) => indexPatternCreationType.getIndexTags(indexName), - query, - this.state.isIncludingSystemIndices - ), - ]); - - // If the search changed, discard this state - if (query !== this.lastQuery) { - return; - } - - this.setState({ - partialMatchedIndices, - exactMatchedIndices, - isLoadingIndices: false, - }); - }; - - onQueryChanged = (e: React.ChangeEvent) => { - const { appendedWildcard } = this.state; - const { target } = e; - - let query = target.value; - if (query.length === 1 && !appendedWildcard && canAppendWildcard(query)) { - query += '*'; - this.setState({ appendedWildcard: true }); - setTimeout(() => target.setSelectionRange(1, 1)); - } else { - if (['', '*'].includes(query) && appendedWildcard) { - query = ''; - this.setState({ appendedWildcard: false }); - } - } - - this.lastQuery = query; - this.setState({ query, showingIndexPatternQueryErrors: !!query.length }); - this.fetchIndices(query); - }; - - renderLoadingState() { - const { isLoadingIndices } = this.state; - - if (!isLoadingIndices) { - return null; - } - - return ( - <> - - - - - ); - } - - renderStatusMessage(matchedIndices: { - allIndices: MatchedItem[]; - exactMatchedIndices: MatchedItem[]; - partialMatchedIndices: MatchedItem[]; - }) { - const { indexPatternCreationType } = this.props; - const { query, isLoadingIndices, indexPatternExists, isIncludingSystemIndices } = this.state; - - if (isLoadingIndices || indexPatternExists) { - return null; - } - - return ( - - ); - } - - renderList({ - visibleIndices, - allIndices, - }: { - visibleIndices: MatchedItem[]; - allIndices: MatchedItem[]; - }) { - const { query, isLoadingIndices, indexPatternExists } = this.state; - - if (isLoadingIndices || indexPatternExists) { - return null; - } - - const indicesToList = query.length ? visibleIndices : allIndices; - return ( - - ); - } - - renderIndexPatternExists() { - const { indexPatternExists, query } = this.state; - - if (!indexPatternExists) { - return null; - } - - return ( - - } - iconType="help" - color="warning" - /> - ); - } - - renderHeader({ exactMatchedIndices: indices }: { exactMatchedIndices: MatchedItem[] }) { - const { goToNextStep, indexPatternCreationType } = this.props; - const { - query, - showingIndexPatternQueryErrors, - indexPatternExists, - indexPatternName, - isIncludingSystemIndices, - } = this.state; - - let containsErrors = false; - const errors = []; - const characterList = this.ILLEGAL_CHARACTERS.slice(0, this.ILLEGAL_CHARACTERS.length - 1).join( - ', ' - ); - - const checkIndices = indexPatternCreationType.checkIndicesForErrors(indices); - - if (!query || !query.length || query === '.' || query === '..') { - // This is an error scenario but do not report an error - containsErrors = true; - } else if (containsIllegalCharacters(query, indexPatterns.ILLEGAL_CHARACTERS)) { - const errorMessage = i18n.translate( - 'indexPatternManagement.createIndexPattern.step.invalidCharactersErrorMessage', - { - defaultMessage: - 'A {indexPatternName} cannot contain spaces or the characters: {characterList}', - values: { characterList, indexPatternName }, - } - ); - - errors.push(errorMessage); - containsErrors = true; - } else if (checkIndices) { - errors.push(...(checkIndices as string[])); - containsErrors = true; - } - - const isInputInvalid = showingIndexPatternQueryErrors && containsErrors && errors.length > 0; - const isNextStepDisabled = containsErrors || indices.length === 0 || indexPatternExists; - - return ( -
goToNextStep(query, canPreselectTimeField(indices))} - isNextStepDisabled={isNextStepDisabled} - onChangeIncludingSystemIndices={this.onChangeIncludingSystemIndices} - isIncludingSystemIndices={isIncludingSystemIndices} - showSystemIndices={this.props.showSystemIndices} - /> - ); - } - - onChangeIncludingSystemIndices = (event: EuiSwitchEvent) => { - this.setState({ isIncludingSystemIndices: event.target.checked }, () => - this.fetchIndices(this.state.query) - ); - }; - - render() { - const { allIndices } = this.props; - const { partialMatchedIndices, exactMatchedIndices, isIncludingSystemIndices } = this.state; - - const matchedIndices = getMatchedIndices( - allIndices, - partialMatchedIndices, - exactMatchedIndices, - isIncludingSystemIndices - ); - - return ( - <> - {this.renderHeader(matchedIndices)} - - {this.renderLoadingState()} - {this.renderIndexPatternExists()} - {this.renderStatusMessage(matchedIndices)} - - {this.renderList(matchedIndices)} - - ); - } -} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap deleted file mode 100644 index 544e3ba983122..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap +++ /dev/null @@ -1,364 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`StepTimeField should disable the action button if an invalid time field is selected 1`] = ` - -`; - -exports[`StepTimeField should enable the action button if the user decides to not select a time field 1`] = ` - -`; - -exports[`StepTimeField should render "Custom index pattern ID already exists" when error is "Conflict" 1`] = ` - -
- - - - - - - } - > -

- -

-
- - - -`; - -exports[`StepTimeField should render a loading state when creating the index pattern 1`] = ` - - - -

- -

-
-
- - - -
-`; - -exports[`StepTimeField should render a selected timeField 1`] = ` - -
- - - - - - - -`; - -exports[`StepTimeField should render advanced options 1`] = ` - -
- - - - - - - -`; - -exports[`StepTimeField should render advanced options with an index pattern id 1`] = ` - -
- - - - - - - -`; - -exports[`StepTimeField should render any error message 1`] = ` - -
- - - - - - - } - > -

- foobar -

-
- - - -`; - -exports[`StepTimeField should render normally 1`] = ` - -
- - - - - - - -`; - -exports[`StepTimeField should render timeFields 1`] = ` - -
- - - - - - - -`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/action_buttons/action_buttons.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/action_buttons/action_buttons.tsx deleted file mode 100644 index 1f5b194fc2549..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/action_buttons/action_buttons.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; - -import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -export const ActionButtons = ({ - goToPreviousStep, - submittable, - createIndexPattern, -}: { - goToPreviousStep: () => void; - submittable: boolean; - createIndexPattern: () => void; -}) => ( - - - - - - - - - - - - -); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/action_buttons/index.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/action_buttons/index.ts deleted file mode 100644 index 56f13b55bc6be..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/action_buttons/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { ActionButtons } from './action_buttons'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/__snapshots__/advanced_options.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/__snapshots__/advanced_options.test.tsx.snap deleted file mode 100644 index a2d2023ea0601..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/__snapshots__/advanced_options.test.tsx.snap +++ /dev/null @@ -1,69 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AdvancedOptions should hide if not showing 1`] = ` -
- - - - -
-`; - -exports[`AdvancedOptions should render normally 1`] = ` -
- - - - - - - } - label={ - - } - labelType="label" - > - - - -
-`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/advanced_options.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/advanced_options.test.tsx deleted file mode 100644 index 0341a760c535f..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/advanced_options.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { AdvancedOptions } from '../advanced_options'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; - -describe('AdvancedOptions', () => { - it('should render normally', () => { - const component = shallowWithI18nProvider( - {}} - onChangeIndexPatternId={() => {}} - /> - ); - - expect(component).toMatchSnapshot(); - }); - - it('should hide if not showing', () => { - const component = shallowWithI18nProvider( - {}} - onChangeIndexPatternId={() => {}} - /> - ); - - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/advanced_options.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/advanced_options.tsx deleted file mode 100644 index 665972c546158..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/advanced_options.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; - -import { EuiForm, EuiFormRow, EuiFieldText, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -interface AdvancedOptionsProps { - isVisible: boolean; - indexPatternId: string; - toggleAdvancedOptions: (e: React.FormEvent) => void; - onChangeIndexPatternId: (e: React.ChangeEvent) => void; -} - -export const AdvancedOptions: React.FC = ({ - isVisible, - indexPatternId, - toggleAdvancedOptions, - onChangeIndexPatternId, -}) => ( -
- - {isVisible ? ( - - ) : ( - - )} - - - {isVisible ? ( - - - } - helpText={ - - } - > - - - - ) : null} -
-); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/index.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/index.ts deleted file mode 100644 index 23ebc3a8c3f66..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/advanced_options/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { AdvancedOptions } from './advanced_options'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/__snapshots__/header.test.tsx.snap deleted file mode 100644 index 9efda4fdac7f9..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/__snapshots__/header.test.tsx.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Header should render normally 1`] = ` -
- -

- -

-
- - - - ki* - , - "indexPatternName": "ki*", - } - } - /> - -
-`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.tsx deleted file mode 100644 index 41cae957def2b..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; - -import { EuiTitle, EuiSpacer, EuiText } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -interface HeaderProps { - indexPattern: string; - indexPatternName: string; -} - -export const Header: React.FC = ({ indexPattern, indexPatternName }) => ( -
- -

- -

-
- - - {indexPattern}, - indexPatternName, - }} - /> - -
-); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/index.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/index.ts deleted file mode 100644 index 8f737b3a42613..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { Header } from './header'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/__snapshots__/time_field.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/__snapshots__/time_field.test.tsx.snap deleted file mode 100644 index 73277b1963626..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/__snapshots__/time_field.test.tsx.snap +++ /dev/null @@ -1,188 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TimeField should render a loading state 1`] = ` - - -

- -

-
- - - } - labelAppend={ - - } - labelType="label" - > - - -
-`; - -exports[`TimeField should render a selected time field 1`] = ` - - -

- -

-
- - - } - labelAppend={ - - - - - - } - labelType="label" - > - - -
-`; - -exports[`TimeField should render normally 1`] = ` - - -

- -

-
- - - } - labelAppend={ - - - - - - } - labelType="label" - > - - -
-`; - -exports[`TimeField should render something if hiding time field 1`] = ` - - -

- -

-
-
-`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/index.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/index.ts deleted file mode 100644 index 7dba3ecc1f75f..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { TimeField } from './time_field'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.test.tsx deleted file mode 100644 index 1f16842f3c89d..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { TimeField } from '../time_field'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; - -describe('TimeField', () => { - it('should render normally', () => { - const component = shallowWithI18nProvider( - {}} - timeFieldOptions={[{ text: '@timestamp', value: '@timestamp' }]} - isLoading={false} - selectedTimeField={''} - onTimeFieldChanged={() => {}} - /> - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render something if hiding time field', () => { - const component = shallowWithI18nProvider( - {}} - timeFieldOptions={[{ text: '@timestamp', value: '@timestamp' }]} - isLoading={false} - selectedTimeField={''} - onTimeFieldChanged={() => {}} - /> - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render a selected time field', () => { - const component = shallowWithI18nProvider( - {}} - timeFieldOptions={[{ text: '@timestamp', value: '@timestamp' }]} - isLoading={false} - selectedTimeField={'@timestamp'} - onTimeFieldChanged={() => {}} - /> - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render a loading state', () => { - const component = shallowWithI18nProvider( - {}} - timeFieldOptions={[{ text: '@timestamp', value: '@timestamp' }]} - isLoading={true} - selectedTimeField={'@timestamp'} - onTimeFieldChanged={() => {}} - /> - ); - - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.tsx deleted file mode 100644 index 7fac76eb847cd..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; - -import { - EuiForm, - EuiFormRow, - EuiSpacer, - EuiLink, - EuiSelect, - EuiText, - EuiLoadingSpinner, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -interface TimeFieldProps { - isVisible: boolean; - fetchTimeFields: () => void; - timeFieldOptions: Array<{ text: string; value?: string }>; - isLoading: boolean; - selectedTimeField?: string; - onTimeFieldChanged: (e: React.ChangeEvent) => void; -} - -export const TimeField: React.FC = ({ - isVisible, - fetchTimeFields, - timeFieldOptions, - isLoading, - selectedTimeField, - onTimeFieldChanged, -}) => ( - - {isVisible ? ( - <> - -

- -

-
- - - } - labelAppend={ - isLoading ? ( - - ) : ( - - - - - - ) - } - > - {isLoading ? ( - - ) : ( - - )} - - - ) : ( - -

- -

-
- )} -
-); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/index.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/index.ts deleted file mode 100644 index 4591dc5ae83dc..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { StepTimeField } from './step_time_field'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx deleted file mode 100644 index fabc6ec69b0ee..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { IndexPatternCreationConfig } from '../../../../../../../plugins/index_pattern_management/public'; -import { IndexPatternField } from '../../../../../../../plugins/data/public'; -import { mockManagementPlugin } from '../../../../mocks'; -import { createComponentWithContext } from '../../../test_utils'; - -import { StepTimeField } from '../step_time_field'; - -jest.mock('./components/header', () => ({ Header: 'Header' })); -jest.mock('./components/time_field', () => ({ TimeField: 'TimeField' })); -jest.mock('./components/advanced_options', () => ({ AdvancedOptions: 'AdvancedOptions' })); -jest.mock('./components/action_buttons', () => ({ ActionButtons: 'ActionButtons' })); -jest.mock('./../../lib', () => ({ - extractTimeFields: jest.requireActual('./../../lib').extractTimeFields, - ensureMinimumTime: async (fields: IndexPatternField) => Promise.resolve(fields), -})); - -const mockIndexPatternCreationType = new IndexPatternCreationConfig({ - type: 'default', - name: 'name', -}); - -const noop = () => {}; -const mockContext = mockManagementPlugin.createIndexPatternManagmentContext(); -const fields = [ - { - name: '@timestamp', - type: 'date', - }, -]; -mockContext.data.indexPatterns = { - create: () => ({}), - getFieldsForWildcard: jest.fn().mockReturnValue(Promise.resolve(fields)), -} as any; - -describe('StepTimeField', () => { - it('should render normally', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - expect(component).toMatchSnapshot(); - }); - - it('should render timeFields', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ - timeFields: [ - { display: '@timestamp', fieldName: '@timestamp' }, - { display: 'name', fieldName: 'name' }, - ], - }); - - expect(component).toMatchSnapshot(); - }); - - it('should render a selected timeField', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ - timeFields: [ - { display: '@timestamp', fieldName: '@timestamp' }, - { display: 'name', fieldName: 'name' }, - ], - selectedTimeField: '@timestamp', - timeFieldSet: true, - }); - - expect(component).toMatchSnapshot(); - }); - - it('should ensure disabled time field options work properly', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ - timeFields: [ - { display: '@timestamp', fieldName: '@timestamp' }, - { display: 'name', fieldName: 'name' }, - ], - }); - - // If the value is undefined, that means the user selected the - // `I don't want to use a Time filter` option - (component.instance() as StepTimeField).onTimeFieldChanged(({ - target: { value: undefined }, - } as unknown) as React.ChangeEvent); - expect(component.state('timeFieldSet')).toBe(true); - - // If the value is an empty string, that means the user selected - // an invalid selection (like the empty selection or the `-----`) - (component.instance() as StepTimeField).onTimeFieldChanged(({ - target: { value: '' }, - } as unknown) as React.ChangeEvent); - expect(component.state('timeFieldSet')).toBe(false); - }); - - it('should disable the action button if an invalid time field is selected', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ - timeFields: [ - { display: '@timestamp', fieldName: '@timestamp' }, - { display: 'name', fieldName: 'name' }, - ], - }); - - (component.instance() as StepTimeField).onTimeFieldChanged(({ - target: { value: '' }, - } as unknown) as React.ChangeEvent); - component.update(); - - expect(component.find('ActionButtons')).toMatchSnapshot(); - }); - - it('should enable the action button if the user decides to not select a time field', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ - timeFields: [ - { display: '@timestamp', fieldName: '@timestamp' }, - { display: 'name', fieldName: 'name' }, - ], - }); - - (component.instance() as StepTimeField).onTimeFieldChanged(({ - target: { value: undefined }, - } as unknown) as React.ChangeEvent); - component.update(); - - expect(component.find('ActionButtons')).toMatchSnapshot(); - }); - - it('should render advanced options', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ showingAdvancedOptions: true }); - - expect(component).toMatchSnapshot(); - }); - - it('should render advanced options with an index pattern id', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ - showingAdvancedOptions: true, - indexPatternId: 'foobar', - }); - - expect(component).toMatchSnapshot(); - }); - - it('should render a loading state when creating the index pattern', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ isCreating: true }); - - expect(component).toMatchSnapshot(); - }); - - it('should render any error message', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ error: 'foobar' }); - - expect(component).toMatchSnapshot(); - }); - - it('should render "Custom index pattern ID already exists" when error is "Conflict"', () => { - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern: noop, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - component.setState({ error: 'Conflict' }); - - expect(component).toMatchSnapshot(); - }); - - it('should remember error thrown by createIndexPatter() prop', async () => { - const createIndexPattern = async () => { - throw new Error('foobar'); - }; - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - await (component.instance() as StepTimeField).createIndexPattern(); - component.update(); - - expect(component.instance().state).toMatchObject({ - error: 'foobar', - }); - }); - - it('should call createIndexPattern with undefined time field when no time filter chosen', async () => { - const createIndexPattern = jest.fn(); - - const component = createComponentWithContext( - StepTimeField, - { - indexPattern: 'ki*', - goToPreviousStep: noop, - createIndexPattern, - indexPatternCreationType: mockIndexPatternCreationType, - }, - mockContext - ); - - await (component.instance() as StepTimeField).fetchTimeFields(); - - expect((component.state() as any).timeFields).toHaveLength(3); - - (component.instance() as StepTimeField).onTimeFieldChanged(({ - target: { value: undefined }, - } as unknown) as React.ChangeEvent); - - await (component.instance() as StepTimeField).createIndexPattern(); - - expect(createIndexPattern).toHaveBeenCalledWith(undefined, ''); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx deleted file mode 100644 index f489132946ec7..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { Component } from 'react'; -import { - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiSpacer, - EuiLoadingSpinner, - EuiHorizontalRule, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { ensureMinimumTime, extractTimeFields } from '../../lib'; - -import { Header } from './components/header'; -import { TimeField } from './components/time_field'; -import { AdvancedOptions } from './components/advanced_options'; -import { ActionButtons } from './components/action_buttons'; -import { context } from '../../../../../../kibana_react/public'; -import { IndexPatternManagmentContextValue } from '../../../../types'; -import { IndexPatternCreationConfig } from '../../../..'; - -interface StepTimeFieldProps { - indexPattern: string; - goToPreviousStep: () => void; - createIndexPattern: (selectedTimeField: string | undefined, indexPatternId: string) => void; - indexPatternCreationType: IndexPatternCreationConfig; - selectedTimeField?: string; -} - -interface StepTimeFieldState { - error: string; - timeFields: TimeFieldConfig[]; - selectedTimeField?: string; - timeFieldSet: boolean; - isAdvancedOptionsVisible: boolean; - isFetchingTimeFields: boolean; - isCreating: boolean; - indexPatternId: string; - indexPatternType: string; - indexPatternName: string; -} - -interface TimeFieldConfig { - display: string; - fieldName?: string; - isDisabled?: boolean; -} - -export class StepTimeField extends Component { - static contextType = context; - - public readonly context!: IndexPatternManagmentContextValue; - - state: StepTimeFieldState = { - error: '', - timeFields: [], - selectedTimeField: undefined, - timeFieldSet: false, - isAdvancedOptionsVisible: false, - isFetchingTimeFields: false, - isCreating: false, - indexPatternId: '', - indexPatternType: '', - indexPatternName: '', - }; - - constructor(props: StepTimeFieldProps) { - super(props); - this.state.indexPatternType = props.indexPatternCreationType.getIndexPatternType() || ''; - this.state.indexPatternName = props.indexPatternCreationType.getIndexPatternName(); - this.state.selectedTimeField = props.selectedTimeField; - if (props.selectedTimeField) { - this.state.timeFieldSet = true; - } - } - - mounted = false; - - componentDidMount() { - this.mounted = true; - this.fetchTimeFields(); - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchTimeFields = async () => { - const { indexPattern: pattern } = this.props; - const { getFetchForWildcardOptions } = this.props.indexPatternCreationType; - - this.setState({ isFetchingTimeFields: true }); - const fields = await ensureMinimumTime( - this.context.services.data.indexPatterns.getFieldsForWildcard({ - pattern, - ...getFetchForWildcardOptions(), - }) - ); - const timeFields = extractTimeFields(fields); - - this.setState({ timeFields, isFetchingTimeFields: false }); - }; - - onTimeFieldChanged = (e: React.ChangeEvent) => { - const value = e.target.value; - - // Find the time field based on the selected value - const timeField = this.state.timeFields.find( - (timeFld: TimeFieldConfig) => timeFld.fieldName === value - ); - - // If the value is an empty string, it's not a valid selection - const validSelection = value !== ''; - - this.setState({ - selectedTimeField: timeField ? (timeField as TimeFieldConfig).fieldName : undefined, - timeFieldSet: validSelection, - }); - }; - - onChangeIndexPatternId = (e: React.ChangeEvent) => { - this.setState({ indexPatternId: e.target.value }); - }; - - toggleAdvancedOptions = () => { - this.setState((state) => ({ - isAdvancedOptionsVisible: !state.isAdvancedOptionsVisible, - })); - }; - - createIndexPattern = async () => { - const { createIndexPattern } = this.props; - const { selectedTimeField, indexPatternId } = this.state; - this.setState({ isCreating: true }); - try { - await createIndexPattern(selectedTimeField, indexPatternId); - } catch (error) { - if (!this.mounted) return; - this.setState({ - error: error instanceof Error ? error.message : String(error), - isCreating: false, - }); - } - }; - - formatErrorMessage(message: string) { - // `createIndexPattern` throws "Conflict" when index pattern ID already exists. - return message === 'Conflict' ? ( - - ) : ( - message - ); - } - - render() { - const { - timeFields, - selectedTimeField, - timeFieldSet, - isAdvancedOptionsVisible, - indexPatternId, - isCreating, - isFetchingTimeFields, - indexPatternName, - } = this.state; - - if (isCreating) { - return ( - - - -

- -

-
-
- - - - -
- ); - } - - const { indexPattern, goToPreviousStep } = this.props; - - const timeFieldOptions = - timeFields.length > 0 - ? [ - { text: '', value: '' }, - ...timeFields.map((timeField: TimeFieldConfig) => ({ - text: timeField.display, - value: timeField.fieldName, - disabled: ((timeFields as unknown) as TimeFieldConfig).isDisabled, - })), - ] - : []; - - const showTimeField = !timeFields || timeFields.length > 1; - const submittable = !showTimeField || timeFieldSet; - const error = this.state.error ? ( - <> - - } - color="danger" - iconType="cross" - > -

{this.formatErrorMessage(this.state.error)}

-
- - - ) : null; - - return ( - <> -
- - - - - - {error} - - - ); - } -} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/constants/index.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/constants/index.ts deleted file mode 100644 index 8bc64bdaa490f..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/constants/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// This isn't ideal. We want to avoid searching for 20 indices -// then filtering out the majority of them because they are system indices. -// We'd like to filter system indices out in the query -// so if we can accomplish that in the future, this logic can go away -export const ESTIMATED_NUMBER_OF_SYSTEM_INDICES = 100; -export const MAX_NUMBER_OF_MATCHING_INDICES = 100; -export const MAX_SEARCH_SIZE = MAX_NUMBER_OF_MATCHING_INDICES + ESTIMATED_NUMBER_OF_SYSTEM_INDICES; - -export const PER_PAGE_INCREMENTS = [5, 10, 20, 50]; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.test.tsx deleted file mode 100644 index a6c7e71445ae3..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.test.tsx +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { CreateIndexPatternWizard } from './create_index_pattern_wizard'; -import { IndexPattern } from '../../../../../plugins/data/public'; -import { mockManagementPlugin } from '../../mocks'; -import { IndexPatternCreationConfig } from '../../'; -import { createComponentWithContext } from '../test_utils'; - -jest.mock('./components/step_index_pattern', () => ({ StepIndexPattern: 'StepIndexPattern' })); -jest.mock('./components/step_time_field', () => ({ StepTimeField: 'StepTimeField' })); -jest.mock('./components/header', () => ({ Header: 'Header' })); -jest.mock('./components/loading_state', () => ({ LoadingState: 'LoadingState' })); -jest.mock('./lib/get_indices', () => ({ - getIndices: () => { - return [{ name: 'kibana' }]; - }, -})); -const routeComponentPropsMock = { - history: { - push: jest.fn(), - } as any, - location: {} as any, - match: {} as any, -}; -const mockContext = mockManagementPlugin.createIndexPatternManagmentContext(); -mockContext.indexPatternManagementStart.creation.getType = () => { - return new IndexPatternCreationConfig({ - type: 'default', - name: 'name', - }); -}; - -describe('CreateIndexPatternWizard', () => { - test(`defaults to the loading state`, () => { - const component = createComponentWithContext( - CreateIndexPatternWizard, - { ...routeComponentPropsMock }, - mockContext - ); - - expect(component).toMatchSnapshot(); - }); - - test('renders the empty state when there are no indices', async () => { - const component = createComponentWithContext( - CreateIndexPatternWizard, - { ...routeComponentPropsMock }, - mockContext - ); - - component.setState({ - isInitiallyLoadingIndices: false, - allIndices: [], - remoteClustersExist: false, - }); - - await component.update(); - expect(component).toMatchSnapshot(); - }); - - test('renders when there are no indices but there are remote clusters', async () => { - const component = createComponentWithContext( - CreateIndexPatternWizard, - { ...routeComponentPropsMock }, - mockContext - ); - - component.setState({ - isInitiallyLoadingIndices: false, - allIndices: [], - remoteClustersExist: true, - }); - - await component.update(); - expect(component).toMatchSnapshot(); - }); - - test('shows system indices even if there are no other indices if the include system indices is toggled', async () => { - const component = createComponentWithContext( - CreateIndexPatternWizard, - { ...routeComponentPropsMock }, - mockContext - ); - - component.setState({ - isInitiallyLoadingIndices: false, - isIncludingSystemIndices: true, - allIndices: [{ name: '.kibana ' }], - }); - - await component.update(); - expect(component).toMatchSnapshot(); - }); - - test('renders index pattern step when there are indices', async () => { - const component = createComponentWithContext( - CreateIndexPatternWizard, - { ...routeComponentPropsMock }, - mockContext - ); - - component.setState({ - isInitiallyLoadingIndices: false, - allIndices: [{ name: 'myIndexPattern' }], - }); - - await component.update(); - expect(component).toMatchSnapshot(); - }); - - test('renders time field step when step is set to 2', async () => { - const component = createComponentWithContext( - CreateIndexPatternWizard, - { ...routeComponentPropsMock }, - mockContext - ); - - component.setState({ - isInitiallyLoadingIndices: false, - allIndices: [{ name: 'myIndexPattern' }], - step: 2, - }); - - await component.update(); - expect(component).toMatchSnapshot(); - }); - - test('invokes the provided services when creating an index pattern', async () => { - const newIndexPatternAndSave = jest.fn().mockImplementation(async () => { - return indexPattern; - }); - const clear = jest.fn(); - mockContext.data.indexPatterns.clearCache = clear; - const indexPattern = ({ - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - fields: [], - _fetchFields: jest.fn(), - } as unknown) as IndexPattern; - mockContext.data.indexPatterns.createAndSave = newIndexPatternAndSave; - mockContext.data.indexPatterns.setDefault = jest.fn(); - - const component = createComponentWithContext( - CreateIndexPatternWizard, - { ...routeComponentPropsMock }, - mockContext - ); - - component.setState({ indexPattern: 'foo' }); - await (component.instance() as CreateIndexPatternWizard).createIndexPattern(undefined, 'id'); - expect(newIndexPatternAndSave).toBeCalled(); - expect(clear).toBeCalledWith('1'); - expect(routeComponentPropsMock.history.push).toBeCalledWith(`/patterns/1`); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx deleted file mode 100644 index 5bc53105dbcf8..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { Component, ReactElement } from 'react'; - -import { EuiGlobalToastList, EuiGlobalToastListToast, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { RouteComponentProps, withRouter } from 'react-router-dom'; -import { DocLinksStart } from 'src/core/public'; -import { StepIndexPattern } from './components/step_index_pattern'; -import { StepTimeField } from './components/step_time_field'; -import { Header } from './components/header'; -import { LoadingState } from './components/loading_state'; - -import { context as contextType } from '../../../../kibana_react/public'; -import { getCreateBreadcrumbs } from '../breadcrumbs'; -import { ensureMinimumTime, getIndices } from './lib'; -import { IndexPatternCreationConfig } from '../..'; -import { IndexPatternManagmentContextValue } from '../../types'; -import { MatchedItem } from './types'; -import { DuplicateIndexPatternError, IndexPattern } from '../../../../data/public'; - -interface CreateIndexPatternWizardState { - step: number; - indexPattern: string; - allIndices: MatchedItem[]; - remoteClustersExist: boolean; - isInitiallyLoadingIndices: boolean; - toasts: EuiGlobalToastListToast[]; - indexPatternCreationType: IndexPatternCreationConfig; - selectedTimeField?: string; - docLinks: DocLinksStart; -} - -export class CreateIndexPatternWizard extends Component< - RouteComponentProps, - CreateIndexPatternWizardState -> { - static contextType = contextType; - - public readonly context!: IndexPatternManagmentContextValue; - - constructor(props: RouteComponentProps, context: IndexPatternManagmentContextValue) { - super(props, context); - - context.services.setBreadcrumbs(getCreateBreadcrumbs()); - - const type = new URLSearchParams(props.location.search).get('type') || undefined; - const indexPattern = new URLSearchParams(props.location.search).get('name') || ''; - - this.state = { - step: 1, - indexPattern, - allIndices: [], - remoteClustersExist: false, - isInitiallyLoadingIndices: true, - toasts: [], - indexPatternCreationType: context.services.indexPatternManagementStart.creation.getType(type), - docLinks: context.services.docLinks, - }; - } - - async UNSAFE_componentWillMount() { - this.fetchData(); - } - - catchAndWarn = async ( - asyncFn: Promise, - errorValue: [] | string[], - errorMsg: ReactElement - ) => { - try { - return await asyncFn; - } catch (errors) { - this.setState((prevState) => ({ - toasts: prevState.toasts.concat([ - { - title: errorMsg, - id: errorMsg.props.id, - color: 'warning', - iconType: 'alert', - }, - ]), - })); - return errorValue; - } - }; - - fetchData = async () => { - const indicesFailMsg = ( - - ); - - const clustersFailMsg = ( - - ); - - // query local and remote indices, updating state independently - ensureMinimumTime( - this.catchAndWarn( - getIndices( - this.context.services.http, - (indexName: string) => this.state.indexPatternCreationType.getIndexTags(indexName), - `*`, - false - ), - - [], - indicesFailMsg - ) - ).then((allIndices: MatchedItem[]) => - this.setState({ allIndices, isInitiallyLoadingIndices: false }) - ); - - this.catchAndWarn( - // if we get an error from remote cluster query, supply fallback value that allows user entry. - // ['a'] is fallback value - getIndices( - this.context.services.http, - (indexName: string) => this.state.indexPatternCreationType.getIndexTags(indexName), - `*:*`, - false - ), - - ['a'], - clustersFailMsg - ).then((remoteIndices: string[] | MatchedItem[]) => - this.setState({ remoteClustersExist: !!remoteIndices.length }) - ); - }; - - createIndexPattern = async (timeFieldName: string | undefined, indexPatternId: string) => { - let emptyPattern: IndexPattern; - const { history } = this.props; - const { indexPattern } = this.state; - - try { - emptyPattern = await this.context.services.data.indexPatterns.createAndSave({ - id: indexPatternId, - title: indexPattern, - timeFieldName, - ...this.state.indexPatternCreationType.getIndexPatternMappings(), - }); - } catch (err) { - if (err instanceof DuplicateIndexPatternError) { - const confirmMessage = i18n.translate( - 'indexPatternManagement.indexPattern.titleExistsLabel', - { - values: { title: emptyPattern!.title }, - defaultMessage: "An index pattern with the title '{title}' already exists.", - } - ); - - const isConfirmed = await this.context.services.overlays.openConfirm(confirmMessage, { - confirmButtonText: i18n.translate( - 'indexPatternManagement.indexPattern.goToPatternButtonLabel', - { - defaultMessage: 'Go to existing pattern', - } - ), - }); - - if (isConfirmed) { - return history.push(`/patterns/${indexPatternId}`); - } else { - return; - } - } else { - throw err; - } - } - - await this.context.services.data.indexPatterns.setDefault(emptyPattern.id as string); - - this.context.services.data.indexPatterns.clearCache(emptyPattern.id as string); - history.push(`/patterns/${emptyPattern.id}`); - }; - - goToTimeFieldStep = (indexPattern: string, selectedTimeField?: string) => { - this.setState({ step: 2, indexPattern, selectedTimeField }); - }; - - goToIndexPatternStep = () => { - this.setState({ step: 1 }); - }; - - renderHeader() { - const { docLinks, indexPatternCreationType } = this.state; - return ( -
- ); - } - - renderContent() { - const { allIndices, isInitiallyLoadingIndices, step, indexPattern } = this.state; - - if (isInitiallyLoadingIndices) { - return ; - } - - const header = this.renderHeader(); - - if (step === 1) { - const { location } = this.props; - const initialQuery = new URLSearchParams(location.search).get('id') || undefined; - - return ( - <> - {header} - - - - ); - } - - if (step === 2) { - return ( - <> - {header} - - - - ); - } - - return null; - } - - removeToast = (id: string) => { - this.setState((prevState) => ({ - toasts: prevState.toasts.filter((toast) => toast.id !== id), - })); - }; - - render() { - const content = this.renderContent(); - - return ( - <> - {content} - { - this.removeToast(id); - }} - toastLifeTimeMs={6000} - /> - - ); - } -} - -export const CreateIndexPatternWizardWithRouter = withRouter(CreateIndexPatternWizard); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/index.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/index.ts deleted file mode 100644 index 52087f388cb97..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { CreateIndexPatternWizardWithRouter } from './create_index_pattern_wizard'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.ts deleted file mode 100644 index e2af9339e57a3..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/extract_time_fields.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { IndexPatternField } from '../../../../../../plugins/data/public'; - -export function extractTimeFields(fields: IndexPatternField[]) { - const dateFields = fields.filter((field) => field.type === 'date'); - const label = i18n.translate( - 'indexPatternManagement.createIndexPattern.stepTime.noTimeFieldsLabel', - { - defaultMessage: "The indices which match this index pattern don't contain any time fields.", - } - ); - - if (dateFields.length === 0) { - return [ - { - display: label, - }, - ]; - } - - const disabledDividerOption = { - isDisabled: true, - display: '───', - fieldName: '', - }; - const noTimeFieldLabel = i18n.translate( - 'indexPatternManagement.createIndexPattern.stepTime.noTimeFieldOptionLabel', - { - defaultMessage: "I don't want to use the time filter", - } - ); - const noTimeFieldOption = { - display: noTimeFieldLabel, - fieldName: undefined, - }; - - return [ - ...dateFields.map((field) => ({ - display: field.name, - fieldName: field.name, - })), - disabledDividerOption, - noTimeFieldOption, - ]; -} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts deleted file mode 100644 index 8d511a30c3532..0000000000000 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export interface MatchedItem { - name: string; - tags: Tag[]; - item: { - name: string; - backing_indices?: string[]; - timestamp_field?: string; - indices?: string[]; - aliases?: string[]; - attributes?: ResolveIndexResponseItemIndexAttrs[]; - data_stream?: string; - }; -} - -export interface ResolveIndexResponse { - indices?: ResolveIndexResponseItemIndex[]; - aliases?: ResolveIndexResponseItemAlias[]; - data_streams?: ResolveIndexResponseItemDataStream[]; -} - -export interface ResolveIndexResponseItem { - name: string; -} - -export interface ResolveIndexResponseItemDataStream extends ResolveIndexResponseItem { - backing_indices: string[]; - timestamp_field: string; -} - -export interface ResolveIndexResponseItemAlias extends ResolveIndexResponseItem { - indices: string[]; -} - -export interface ResolveIndexResponseItemIndex extends ResolveIndexResponseItem { - aliases?: string[]; - attributes?: ResolveIndexResponseItemIndexAttrs[]; - data_stream?: string; -} - -export enum ResolveIndexResponseItemIndexAttrs { - OPEN = 'open', - CLOSED = 'closed', - HIDDEN = 'hidden', - FROZEN = 'frozen', -} - -export interface Tag { - name: string; - key: string; - color: string; -} diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index 6609605da87d1..a8f89b471e4eb 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -25,6 +25,7 @@ import { useKibana } from '../../../../../plugins/kibana_react/public'; import { IndexPatternManagmentContext } from '../../types'; import { Tabs } from './tabs'; import { IndexHeader } from './index_header'; +import { getTags } from '../utils'; export interface EditIndexPatternProps extends RouteComponentProps { indexPattern: IndexPattern; @@ -57,7 +58,6 @@ export const EditIndexPattern = withRouter( ({ indexPattern, history, location }: EditIndexPatternProps) => { const { uiSettings, - indexPatternManagementStart, overlays, chrome, data, @@ -77,13 +77,8 @@ export const EditIndexPattern = withRouter( }, [indexPattern]); useEffect(() => { - const indexPatternTags = - indexPatternManagementStart.list.getIndexPatternTags( - indexPattern, - indexPattern.id === defaultIndex - ) || []; - setTags(indexPatternTags); - }, [defaultIndex, indexPattern, indexPatternManagementStart.list]); + setTags(getTags(indexPattern, indexPattern.id === defaultIndex)); + }, [defaultIndex, indexPattern]); const setDefaultPattern = useCallback(() => { uiSettings.set('defaultIndex', indexPattern.id); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index 6d37e8f13d6b3..2f288dc072914 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { IndexPatternField, IndexPattern, IndexPatternType } from 'src/plugins/data/public'; import { IndexedFieldsTable } from './indexed_fields_table'; -import { RollupIndexPatternListConfig } from '../../../service/list'; +import { getFieldInfo } from '../../utils'; jest.mock('@elastic/eui', () => ({ EuiFlexGroup: 'eui-flex-group', @@ -30,7 +30,7 @@ const helpers = { editField: (fieldName: string) => {}, deleteField: (fieldName: string) => {}, // getFieldInfo handles non rollups as well - getFieldInfo: new RollupIndexPatternListConfig().getFieldInfo, + getFieldInfo, }; const indexPattern = ({ diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx index 4d99bd504cd0f..6594d677884c2 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -35,6 +35,7 @@ import { SourceFiltersTable } from '../source_filters_table'; import { IndexedFieldsTable } from '../indexed_fields_table'; import { ScriptedFieldsTable } from '../scripted_fields_table'; import { getTabs, getPath, convertToEuiSelectOption } from './utils'; +import { getFieldInfo } from '../../utils'; interface TabsProps extends Pick { indexPattern: IndexPattern; @@ -81,7 +82,6 @@ export function Tabs({ }: TabsProps) { const { uiSettings, - indexPatternManagementStart, docLinks, indexPatternFieldEditor, } = useKibana().services; @@ -227,7 +227,7 @@ export function Tabs({ helpers={{ editField: openFieldEditor, deleteField, - getFieldInfo: indexPatternManagementStart.list.getFieldInfo, + getFieldInfo, }} /> )} @@ -280,7 +280,6 @@ export function Tabs({ getFilterSection, history, indexPattern, - indexPatternManagementStart.list.getFieldInfo, indexedFieldTypeFilter, refreshFilters, scriptedFieldLanguageFilter, @@ -293,15 +292,13 @@ export function Tabs({ const euiTabs: EuiTabbedContentTab[] = useMemo( () => - getTabs(indexPattern, fieldFilter, indexPatternManagementStart.list).map( - (tab: Pick) => { - return { - ...tab, - content: getContent(tab.id), - }; - } - ), - [fieldFilter, getContent, indexPattern, indexPatternManagementStart.list] + getTabs(indexPattern, fieldFilter).map((tab: Pick) => { + return { + ...tab, + content: getContent(tab.id), + }; + }), + [fieldFilter, getContent, indexPattern] ); const [selectedTabId, setSelectedTabId] = useState(euiTabs[0].id); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts index afa786b4d71e6..76bb86eb88d9b 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts @@ -9,8 +9,8 @@ import { Dictionary, countBy, defaults, uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; import { IndexPattern, IndexPatternField } from '../../../../../../plugins/data/public'; -import { IndexPatternManagementStart } from '../../../../../../plugins/index_pattern_management/public'; import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from '../constants'; +import { areScriptedFieldsEnabled } from '../../utils'; function filterByName(items: IndexPatternField[], filter: string) { const lowercaseFilter = (filter || '').toLowerCase(); @@ -68,11 +68,7 @@ function getTitle(type: string, filteredCount: Dictionary, totalCount: D return title + count; } -export function getTabs( - indexPattern: IndexPattern, - fieldFilter: string, - indexPatternListProvider: IndexPatternManagementStart['list'] -) { +export function getTabs(indexPattern: IndexPattern, fieldFilter: string) { const totalCount = getCounts(indexPattern.fields.getAll(), indexPattern.getSourceFiltering()); const filteredCount = getCounts( indexPattern.fields.getAll(), @@ -88,7 +84,7 @@ export function getTabs( 'data-test-subj': 'tab-indexedFields', }); - if (indexPatternListProvider.areScriptedFieldsEnabled(indexPattern)) { + if (areScriptedFieldsEnabled(indexPattern)) { tabs.push({ name: getTitle('scripted', filteredCount, totalCount), id: TAB_SCRIPTED_FIELDS, diff --git a/src/plugins/index_pattern_management/public/components/index.ts b/src/plugins/index_pattern_management/public/components/index.ts index 022f32fb3defc..bbe10af809c9f 100644 --- a/src/plugins/index_pattern_management/public/components/index.ts +++ b/src/plugins/index_pattern_management/public/components/index.ts @@ -13,4 +13,3 @@ export { CreateEditField, CreateEditFieldContainer, } from './edit_index_pattern'; -export { CreateIndexPatternWizardWithRouter } from './create_index_pattern_wizard'; diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/index.ts b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/index.ts deleted file mode 100644 index 4e19a60d5a769..0000000000000 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { EmptyState } from './empty_state'; diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx index 6405a81282471..ef99be4df7cb8 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx @@ -8,6 +8,7 @@ import { EuiBadge, + EuiButton, EuiBadgeGroup, EuiButtonEmpty, EuiInMemoryTable, @@ -15,19 +16,14 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { RouteComponentProps, withRouter } from 'react-router-dom'; +import { RouteComponentProps, withRouter, useLocation } from 'react-router-dom'; import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { reactRouterNavigate, useKibana } from '../../../../../plugins/kibana_react/public'; import { IndexPatternManagmentContext } from '../../types'; -import { CreateButton } from '../create_button'; -import { IndexPatternCreationOption, IndexPatternTableItem } from '../types'; +import { IndexPatternTableItem } from '../types'; import { getIndexPatterns } from '../utils'; import { getListBreadcrumbs } from '../breadcrumbs'; -import { EmptyState } from './empty_state'; -import { MatchedItem, ResolveIndexResponseItemAlias } from '../create_index_pattern_wizard/types'; -import { EmptyIndexPatternPrompt } from './empty_index_pattern_prompt'; -import { getIndices } from '../create_index_pattern_wizard/lib'; const pagination = { initialPageSize: 10, @@ -56,67 +52,45 @@ const title = i18n.translate('indexPatternManagement.indexPatternTable.title', { interface Props extends RouteComponentProps { canSave: boolean; + showCreateDialog?: boolean; } -export const IndexPatternTable = ({ canSave, history }: Props) => { +export const IndexPatternTable = ({ + history, + canSave, + showCreateDialog: showCreateDialogProp = false, +}: Props) => { const { setBreadcrumbs, uiSettings, indexPatternManagementStart, chrome, - docLinks, - application, - http, data, + IndexPatternEditor, } = useKibana().services; const [indexPatterns, setIndexPatterns] = useState([]); - const [creationOptions, setCreationOptions] = useState([]); - const [sources, setSources] = useState([]); - const [remoteClustersExist, setRemoteClustersExist] = useState(false); - const [isLoadingSources, setIsLoadingSources] = useState(true); const [isLoadingIndexPatterns, setIsLoadingIndexPatterns] = useState(true); + const [showCreateDialog, setShowCreateDialog] = useState(showCreateDialogProp); setBreadcrumbs(getListBreadcrumbs()); useEffect(() => { (async function () { - const options = await indexPatternManagementStart.creation.getIndexPatternCreationOptions( - history.push - ); const gettedIndexPatterns: IndexPatternTableItem[] = await getIndexPatterns( uiSettings.get('defaultIndex'), - indexPatternManagementStart, data.indexPatterns ); - setIsLoadingIndexPatterns(false); - setCreationOptions(options); setIndexPatterns(gettedIndexPatterns); + setIsLoadingIndexPatterns(false); + if (gettedIndexPatterns.length === 0) { + setShowCreateDialog(true); + } })(); - }, [history.push, indexPatterns.length, indexPatternManagementStart, uiSettings, data]); - - const removeAliases = (item: MatchedItem) => - !((item as unknown) as ResolveIndexResponseItemAlias).indices; - - const loadSources = () => { - getIndices(http, () => [], '*', false).then((dataSources) => - setSources(dataSources.filter(removeAliases)) - ); - getIndices(http, () => [], '*:*', false).then((dataSources) => - setRemoteClustersExist(!!dataSources.filter(removeAliases).length) - ); - }; - - useEffect(() => { - getIndices(http, () => [], '*', false).then((dataSources) => { - setSources(dataSources.filter(removeAliases)); - setIsLoadingSources(false); - }); - getIndices(http, () => [], '*:*', false).then((dataSources) => - setRemoteClustersExist(!!dataSources.filter(removeAliases).length) - ); - }, [http, creationOptions]); + }, [indexPatternManagementStart, uiSettings, data]); chrome.docTitle.change(title); + const isRollup = new URLSearchParams(useLocation().search).get('type') === 'rollup'; + const columns = [ { field: 'title', @@ -150,43 +124,36 @@ export const IndexPatternTable = ({ canSave, history }: Props) => { ]; const createButton = canSave ? ( - + setShowCreateDialog(true)} + data-test-subj="createIndexPatternButton" + > - + ) : ( <> ); - if (isLoadingSources || isLoadingIndexPatterns) { + if (isLoadingIndexPatterns) { return <>; } - const hasDataIndices = sources.some(({ name }: MatchedItem) => !name.startsWith('.')); - - if (!indexPatterns.length) { - if (!hasDataIndices && !remoteClustersExist) { - return ( - - ); - } else { - return ( - - ); - } - } + const displayIndexPatternEditor = showCreateDialog ? ( + { + history.push(`patterns/${indexPattern.id}`); + }} + onCancel={() => setShowCreateDialog(false)} + defaultTypeIsRollup={isRollup} + /> + ) : ( + <> + ); return (
@@ -214,6 +181,7 @@ export const IndexPatternTable = ({ canSave, history }: Props) => { sorting={sorting} search={search} /> + {displayIndexPatternEditor}
); }; diff --git a/src/plugins/index_pattern_management/public/components/utils.test.ts b/src/plugins/index_pattern_management/public/components/utils.test.ts index 15e0a65390f4d..6215a8dd15c4c 100644 --- a/src/plugins/index_pattern_management/public/components/utils.test.ts +++ b/src/plugins/index_pattern_management/public/components/utils.test.ts @@ -7,7 +7,6 @@ */ import { IndexPatternsContract } from 'src/plugins/data/public'; import { getIndexPatterns } from './utils'; -import { mockManagementPlugin } from '../mocks'; const indexPatternContractMock = ({ getIdsWithTitle: jest.fn().mockReturnValue( @@ -25,13 +24,7 @@ const indexPatternContractMock = ({ get: jest.fn().mockReturnValue(Promise.resolve({})), } as unknown) as jest.Mocked; -const mockManagementPluginStart = mockManagementPlugin.createStartContract(); - test('getting index patterns', async () => { - const indexPatterns = await getIndexPatterns( - 'test', - mockManagementPluginStart, - indexPatternContractMock - ); + const indexPatterns = await getIndexPatterns('test', indexPatternContractMock); expect(indexPatterns).toMatchSnapshot(); }); diff --git a/src/plugins/index_pattern_management/public/components/utils.ts b/src/plugins/index_pattern_management/public/components/utils.ts index 68e78199798b4..6520de95028c6 100644 --- a/src/plugins/index_pattern_management/public/components/utils.ts +++ b/src/plugins/index_pattern_management/public/components/utils.ts @@ -7,11 +7,29 @@ */ import { IndexPatternsContract } from 'src/plugins/data/public'; -import { IndexPatternManagementStart } from '../plugin'; +import { IndexPattern, IFieldType } from 'src/plugins/data/public'; +import { i18n } from '@kbn/i18n'; + +const defaultIndexPatternListName = i18n.translate( + 'indexPatternManagement.editIndexPattern.list.defaultIndexPatternListName', + { + defaultMessage: 'Default', + } +); + +const rollupIndexPatternListName = i18n.translate( + 'indexPatternManagement.editIndexPattern.list.rollupIndexPatternListName', + { + defaultMessage: 'Rollup', + } +); + +const isRollup = (indexPattern: IndexPattern) => { + return indexPattern.type === 'rollup'; +}; export async function getIndexPatterns( defaultIndex: string, - indexPatternManagementStart: IndexPatternManagementStart, indexPatternsService: IndexPatternsContract ) { const existingIndexPatterns = await indexPatternsService.getIdsWithTitle(true); @@ -19,10 +37,7 @@ export async function getIndexPatterns( existingIndexPatterns.map(async ({ id, title }) => { const isDefault = defaultIndex === id; const pattern = await indexPatternsService.get(id); - const tags = (indexPatternManagementStart as IndexPatternManagementStart).list.getIndexPatternTags( - pattern, - isDefault - ); + const tags = getTags(pattern, isDefault); return { id, @@ -49,3 +64,78 @@ export async function getIndexPatterns( }) || [] ); } + +export const getTags = (indexPattern: IndexPattern, isDefault: boolean) => { + const tags = []; + if (isDefault) { + tags.push({ + key: 'default', + name: defaultIndexPatternListName, + }); + } + if (isRollup(indexPattern)) { + tags.push({ + key: 'rollup', + name: rollupIndexPatternListName, + }); + } + return tags; +}; + +export const areScriptedFieldsEnabled = (indexPattern: IndexPattern) => { + return !isRollup(indexPattern); +}; + +export const getFieldInfo = (indexPattern: IndexPattern, field: IFieldType) => { + if (!isRollup(indexPattern)) { + return []; + } + + const allAggs = indexPattern.typeMeta && indexPattern.typeMeta.aggs; + const fieldAggs = allAggs && Object.keys(allAggs).filter((agg) => allAggs[agg][field.name]); + + if (!fieldAggs || !fieldAggs.length) { + return []; + } + + return ['Rollup aggregations:'].concat( + fieldAggs.map((aggName) => { + const agg = allAggs![aggName][field.name]; + switch (aggName) { + case 'date_histogram': + return i18n.translate( + 'indexPatternManagement.editIndexPattern.list.dateHistogramSummary', + { + defaultMessage: '{aggName} (interval: {interval}, {delay} {time_zone})', + values: { + aggName, + interval: agg.fixed_interval, + delay: agg.delay + ? i18n.translate( + 'indexPatternManagement.editIndexPattern.list.DateHistogramDelaySummary', + { + defaultMessage: 'delay: {delay},', + values: { + delay: agg.delay, + }, + } + ) + : '', + time_zone: agg.time_zone, + }, + } + ); + case 'histogram': + return i18n.translate('indexPatternManagement.editIndexPattern.list.histogramSummary', { + defaultMessage: '{aggName} (interval: {interval})', + values: { + aggName, + interval: agg.interval, + }, + }); + default: + return aggName; + } + }) + ); +}; diff --git a/src/plugins/index_pattern_management/public/index.ts b/src/plugins/index_pattern_management/public/index.ts index 726c055d1b8c3..45a2f0b5a468b 100644 --- a/src/plugins/index_pattern_management/public/index.ts +++ b/src/plugins/index_pattern_management/public/index.ts @@ -24,9 +24,3 @@ export { IndexPatternManagementSetup, IndexPatternManagementStart } from './plug export function plugin(initializerContext: PluginInitializerContext) { return new IndexPatternManagementPlugin(initializerContext); } - -export { - IndexPatternCreationConfig, - IndexPatternCreationOption, - IndexPatternListConfig, -} from './service'; diff --git a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx index ec5b7c74020a5..e493595c848cf 100644 --- a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx +++ b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx @@ -20,7 +20,6 @@ import { IndexPatternTableWithRouter, EditIndexPatternContainer, CreateEditFieldContainer, - CreateIndexPatternWizardWithRouter, } from '../components'; import { IndexPatternManagementStartDependencies, IndexPatternManagementStart } from '../plugin'; import { IndexPatternManagmentContext } from '../types'; @@ -41,7 +40,7 @@ export async function mountManagementSection( ) { const [ { chrome, application, uiSettings, notifications, overlays, http, docLinks }, - { data, indexPatternFieldEditor }, + { data, indexPatternFieldEditor, indexPatternEditor }, indexPatternManagementStart, ] = await getStartServices(); const canSave = Boolean(application.capabilities.indexPatterns.save); @@ -63,6 +62,7 @@ export async function mountManagementSection( indexPatternManagementStart: indexPatternManagementStart as IndexPatternManagementStart, setBreadcrumbs: params.setBreadcrumbs, fieldFormatEditors: indexPatternFieldEditor.fieldFormatEditors, + IndexPatternEditor: indexPatternEditor.IndexPatternEditorComponent, }; ReactDOM.render( @@ -71,7 +71,7 @@ export async function mountManagementSection( - + diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts index 7671a532d1cb8..5bcca1f09029f 100644 --- a/src/plugins/index_pattern_management/public/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -12,6 +12,7 @@ import { managementPluginMock } from '../../management/public/mocks'; import { urlForwardingPluginMock } from '../../url_forwarding/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; import { indexPatternFieldEditorPluginMock } from '../../index_pattern_field_editor/public/mocks'; +import { indexPatternEditorPluginMock } from '../../index_pattern_editor/public/mocks'; import { IndexPatternManagementSetup, IndexPatternManagementStart, @@ -19,19 +20,9 @@ import { } from './plugin'; import { IndexPatternManagmentContext } from './types'; -const createSetupContract = (): IndexPatternManagementSetup => {}; +const createSetupContract = (): IndexPatternManagementSetup => ({}); -const createStartContract = (): IndexPatternManagementStart => ({ - creation: { - getType: jest.fn(), - getIndexPatternCreationOptions: jest.fn(), - } as any, - list: { - getIndexPatternTags: jest.fn(), - getFieldInfo: jest.fn(), - areScriptedFieldsEnabled: jest.fn(), - } as any, -}); +const createStartContract = (): IndexPatternManagementStart => ({}); const createInstance = async () => { const plugin = new IndexPatternManagementPlugin({} as PluginInitializerContext); @@ -40,11 +31,7 @@ const createInstance = async () => { management: managementPluginMock.createSetupContract(), urlForwarding: urlForwardingPluginMock.createSetupContract(), }); - const doStart = () => - plugin.start(coreMock.createStart(), { - data: dataPluginMock.createStartContract(), - indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(), - }); + const doStart = () => plugin.start(); return { plugin, @@ -84,6 +71,8 @@ const createIndexPatternManagmentContext = (): { indexPatternManagementStart: createStartContract(), setBreadcrumbs: () => {}, fieldFormatEditors: indexPatternFieldEditor.fieldFormatEditors, + IndexPatternEditor: indexPatternEditorPluginMock.createStartContract() + .IndexPatternEditorComponent, }; }; diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index d254691a0270d..9527a04a6b0e2 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -7,17 +7,13 @@ */ import { i18n } from '@kbn/i18n'; -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { PluginInitializerContext, CoreSetup, Plugin } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { UrlForwardingSetup } from '../../url_forwarding/public'; -import { - IndexPatternManagementService, - IndexPatternManagementServiceSetup, - IndexPatternManagementServiceStart, -} from './service'; import { ManagementSetup } from '../../management/public'; import { IndexPatternFieldEditorStart } from '../../index_pattern_field_editor/public'; +import { IndexPatternEditorStart } from '../../index_pattern_editor/public'; export interface IndexPatternManagementSetupDependencies { management: ManagementSetup; @@ -27,11 +23,14 @@ export interface IndexPatternManagementSetupDependencies { export interface IndexPatternManagementStartDependencies { data: DataPublicPluginStart; indexPatternFieldEditor: IndexPatternFieldEditorStart; + indexPatternEditor: IndexPatternEditorStart; } -export type IndexPatternManagementSetup = IndexPatternManagementServiceSetup; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IndexPatternManagementSetup {} -export type IndexPatternManagementStart = IndexPatternManagementServiceStart; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IndexPatternManagementStart {} const sectionsHeader = i18n.translate('indexPatternManagement.indexPattern.sectionsHeader', { defaultMessage: 'Index Patterns', @@ -47,8 +46,6 @@ export class IndexPatternManagementPlugin IndexPatternManagementSetupDependencies, IndexPatternManagementStartDependencies > { - private readonly indexPatternManagementService = new IndexPatternManagementService(); - constructor(initializerContext: PluginInitializerContext) {} public setup( @@ -80,16 +77,12 @@ export class IndexPatternManagementPlugin return mountManagementSection(core.getStartServices, params); }, }); + return {}; } - public start(core: CoreStart, plugins: IndexPatternManagementStartDependencies) { - return this.indexPatternManagementService.start({ - httpClient: core.http, - uiSettings: core.uiSettings, - }); + public start() { + return {}; } - public stop() { - this.indexPatternManagementService.stop(); - } + public stop() {} } diff --git a/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts b/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts deleted file mode 100644 index d1fc2fa242eb1..0000000000000 --- a/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { RollupPrompt } from './rollup_prompt'; diff --git a/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx b/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx deleted file mode 100644 index 81fcdaedb90c9..0000000000000 --- a/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; - -import { EuiCallOut } from '@elastic/eui'; - -export const RollupPrompt = () => ( - -

- {i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text', - { - defaultMessage: - "Kibana's support for rollup index patterns is in beta. You might encounter issues using " + - 'these patterns in saved searches, visualizations, and dashboards. They are not supported in ' + - 'some advanced features, such as Timelion, and Machine Learning.', - } - )} -

-

- {i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text', - { - defaultMessage: - 'You can match a rollup index pattern against one rollup index and zero or more regular ' + - 'indices. A rollup index pattern has limited metrics, fields, intervals, and aggregations. A ' + - 'rollup index is limited to indices that have one job configuration, or multiple jobs with ' + - 'compatible configurations.', - } - )} -

-
-); diff --git a/src/plugins/index_pattern_management/public/service/creation/config.ts b/src/plugins/index_pattern_management/public/service/creation/config.ts deleted file mode 100644 index 6d7e3aa9e5ede..0000000000000 --- a/src/plugins/index_pattern_management/public/service/creation/config.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { MatchedItem } from '../../components/create_index_pattern_wizard/types'; - -const indexPatternTypeName = i18n.translate( - 'indexPatternManagement.editIndexPattern.createIndex.defaultTypeName', - { defaultMessage: 'index pattern' } -); - -const indexPatternButtonText = i18n.translate( - 'indexPatternManagement.editIndexPattern.createIndex.defaultButtonText', - { defaultMessage: 'Standard index pattern' } -); - -const indexPatternButtonDescription = i18n.translate( - 'indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription', - { defaultMessage: 'Perform full aggregations against any data' } -); - -export type UrlHandler = (url: string) => void; - -export interface IndexPatternCreationOption { - text: string; - description: string; - testSubj: string; - onClick: () => void; - isBeta?: boolean; -} - -export class IndexPatternCreationConfig { - public readonly key = 'default'; - - protected type?: string; - protected name: string; - protected showSystemIndices: boolean; - protected httpClient: object | null; - protected isBeta: boolean; - - constructor({ - type = undefined, - name = indexPatternTypeName, - showSystemIndices = true, - httpClient = null, - isBeta = false, - }: { - type?: string; - name?: string; - showSystemIndices?: boolean; - httpClient?: object | null; - isBeta?: boolean; - }) { - this.type = type; - this.name = name; - this.showSystemIndices = showSystemIndices; - this.httpClient = httpClient; - this.isBeta = isBeta; - } - - public getIndexPatternCreationOption(urlHandler: UrlHandler): IndexPatternCreationOption { - return { - text: indexPatternButtonText, - description: indexPatternButtonDescription, - testSubj: `createStandardIndexPatternButton`, - onClick: () => { - urlHandler('/create'); - }, - }; - } - - public getIndexPatternType() { - return this.type; - } - - public getIndexPatternName() { - return this.name; - } - - public getIsBeta() { - return this.isBeta; - } - - public getShowSystemIndices() { - return this.showSystemIndices; - } - - public getIndexTags(indexName: string) { - return []; - } - - public checkIndicesForErrors(indices: MatchedItem[]) { - return undefined; - } - - public getIndexPatternMappings() { - return {}; - } - - public renderPrompt() { - return null; - } - - public getFetchForWildcardOptions() { - return {}; - } -} diff --git a/src/plugins/index_pattern_management/public/service/creation/index.ts b/src/plugins/index_pattern_management/public/service/creation/index.ts deleted file mode 100644 index e1f464b01e550..0000000000000 --- a/src/plugins/index_pattern_management/public/service/creation/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { IndexPatternCreationConfig, IndexPatternCreationOption } from './config'; -export { IndexPatternCreationManager } from './manager'; -// @ts-ignore -export { RollupIndexPatternCreationConfig } from './rollup_creation_config'; diff --git a/src/plugins/index_pattern_management/public/service/creation/manager.ts b/src/plugins/index_pattern_management/public/service/creation/manager.ts deleted file mode 100644 index cc2285bbfcafb..0000000000000 --- a/src/plugins/index_pattern_management/public/service/creation/manager.ts +++ /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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { once } from 'lodash'; -import { HttpStart, CoreStart } from '../../../../../core/public'; -import { IndexPatternCreationConfig, UrlHandler, IndexPatternCreationOption } from './config'; -import { CONFIG_ROLLUPS } from '../../constants'; -// @ts-ignore -import { RollupIndexPatternCreationConfig } from './rollup_creation_config'; - -interface IndexPatternCreationManagerStart { - httpClient: HttpStart; - uiSettings: CoreStart['uiSettings']; -} - -export class IndexPatternCreationManager { - start({ httpClient, uiSettings }: IndexPatternCreationManagerStart) { - const getConfigs = once(() => { - const configs: IndexPatternCreationConfig[] = []; - configs.push(new IndexPatternCreationConfig({ httpClient })); - - if (uiSettings.isDeclared(CONFIG_ROLLUPS) && uiSettings.get(CONFIG_ROLLUPS)) { - configs.push(new RollupIndexPatternCreationConfig({ httpClient })); - } - - return configs; - }); - - const getType = (key: string | undefined): IndexPatternCreationConfig => { - const configs = getConfigs(); - if (key) { - const index = configs.findIndex((config) => config.key === key); - const config = configs[index]; - - if (config) { - return config; - } else { - throw new Error(`Index pattern creation type not found: ${key}`); - } - } else { - return getType('default'); - } - }; - - return { - getType, - getIndexPatternCreationOptions: async (urlHandler: UrlHandler) => { - const options: IndexPatternCreationOption[] = []; - - await Promise.all( - getConfigs().map(async (config) => { - const option = config.getIndexPatternCreationOption - ? await config.getIndexPatternCreationOption(urlHandler) - : null; - if (option) { - options.push(option); - } - }) - ); - - return options; - }, - }; - } -} diff --git a/src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js b/src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js deleted file mode 100644 index 2a85dfa01143c..0000000000000 --- a/src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; - -import { RollupPrompt } from './components/rollup_prompt'; -import { IndexPatternCreationConfig } from '.'; - -const rollupIndexPatternTypeName = i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName', - { defaultMessage: 'rollup index pattern' } -); - -const rollupIndexPatternButtonText = i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText', - { defaultMessage: 'Rollup index pattern' } -); - -const rollupIndexPatternButtonDescription = i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription', - { defaultMessage: 'Perform limited aggregations against summarized data' } -); - -const rollupIndexPatternNoMatchError = i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError', - { defaultMessage: 'Rollup index pattern error: must match one rollup index' } -); - -const rollupIndexPatternTooManyMatchesError = i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError', - { defaultMessage: 'Rollup index pattern error: can only match one rollup index' } -); - -const rollupIndexPatternIndexLabel = i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel', - { defaultMessage: 'Rollup' } -); - -export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig { - key = 'rollup'; - - constructor(options) { - super({ - type: 'rollup', - name: rollupIndexPatternTypeName, - showSystemIndices: false, - isBeta: true, - ...options, - }); - - this.rollupIndex = null; - this.rollupJobs = []; - this.rollupIndicesCapabilities = {}; - this.rollupIndices = []; - } - - async setRollupIndices() { - try { - // This is a hack intended to prevent the getRollupIndices() request from being sent if - // we're on /logout. There is a race condition that can arise on that page, whereby this - // request resolves after the logout request resolves, and un-clears the session ID. - const isAnonymous = this.httpClient.anonymousPaths.isAnonymous(window.location.pathname); - if (!isAnonymous) { - const response = await this.httpClient.get('/api/rollup/indices'); - this.rollupIndicesCapabilities = response || {}; - } - - this.rollupIndices = Object.keys(this.rollupIndicesCapabilities); - } catch (e) { - // Silently swallow failure responses such as expired trials - } - } - - async getIndexPatternCreationOption(urlHandler) { - await this.setRollupIndices(); - return this.rollupIndices && this.rollupIndices.length - ? { - text: rollupIndexPatternButtonText, - description: rollupIndexPatternButtonDescription, - testSubj: `createRollupIndexPatternButton`, - isBeta: this.isBeta, - onClick: () => { - urlHandler('/create?type=rollup'); - }, - } - : null; - } - - isRollupIndex = (indexName) => { - return this.rollupIndices.includes(indexName); - }; - - getIndexTags(indexName) { - return this.isRollupIndex(indexName) - ? [ - { - key: this.type, - name: rollupIndexPatternIndexLabel, - color: 'primary', - }, - ] - : []; - } - - checkIndicesForErrors = (indices) => { - this.rollupIndex = null; - - if (!indices || !indices.length) { - return; - } - - const rollupIndices = indices.filter((index) => this.isRollupIndex(index.name)); - - if (!rollupIndices.length) { - return [rollupIndexPatternNoMatchError]; - } else if (rollupIndices.length > 1) { - return [rollupIndexPatternTooManyMatchesError]; - } - - const rollupIndexName = rollupIndices[0].name; - const error = this.rollupIndicesCapabilities[rollupIndexName].error; - - if (error) { - const errorMessage = i18n.translate( - 'indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError', - { - defaultMessage: 'Rollup index pattern error: {error}', - values: { - error, - }, - } - ); - return [errorMessage]; - } - - this.rollupIndex = rollupIndexName; - }; - - getIndexPatternMappings = () => { - return this.rollupIndex - ? { - type: this.type, - typeMeta: { - params: { - rollup_index: this.rollupIndex, - }, - aggs: this.rollupIndicesCapabilities[this.rollupIndex].aggs, - }, - } - : {}; - }; - - renderPrompt = () => { - return ; - }; - - getFetchForWildcardOptions = () => { - return { - type: this.type, - rollupIndex: this.rollupIndex, - }; - }; -} diff --git a/src/plugins/index_pattern_management/public/service/index.ts b/src/plugins/index_pattern_management/public/service/index.ts deleted file mode 100644 index 8bf09cf544648..0000000000000 --- a/src/plugins/index_pattern_management/public/service/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './index_pattern_management_service'; -export { IndexPatternCreationConfig, IndexPatternCreationOption } from './creation'; -export { IndexPatternListConfig } from './list'; diff --git a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts deleted file mode 100644 index 25a36faa1c3e3..0000000000000 --- a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { HttpStart, CoreStart } from '../../../../core/public'; -import { IndexPatternCreationManager } from './creation'; -import { IndexPatternListManager } from './list'; - -interface StartDependencies { - httpClient: HttpStart; - uiSettings: CoreStart['uiSettings']; -} - -/** - * Index patterns management service - * - * @internal - */ -export class IndexPatternManagementService { - indexPatternCreationManager: IndexPatternCreationManager; - indexPatternListConfig: IndexPatternListManager; - - constructor() { - this.indexPatternCreationManager = new IndexPatternCreationManager(); - this.indexPatternListConfig = new IndexPatternListManager(); - } - - public setup() {} - - public start({ httpClient, uiSettings }: StartDependencies) { - return { - creation: this.indexPatternCreationManager.start({ httpClient, uiSettings }), - list: this.indexPatternListConfig.start({ uiSettings }), - }; - } - - public stop() { - // nothing to do here yet. - } -} - -/** @internal */ -export type IndexPatternManagementServiceSetup = ReturnType; -export type IndexPatternManagementServiceStart = ReturnType; diff --git a/src/plugins/index_pattern_management/public/service/list/config.ts b/src/plugins/index_pattern_management/public/service/list/config.ts deleted file mode 100644 index 4be27fc47d0db..0000000000000 --- a/src/plugins/index_pattern_management/public/service/list/config.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { IndexPattern, IndexPatternField, IndexPatternType } from '../../../../data/public'; - -export interface IndexPatternTag { - key: string; - name: string; -} - -const defaultIndexPatternListName = i18n.translate( - 'indexPatternManagement.editIndexPattern.list.defaultIndexPatternListName', - { - defaultMessage: 'Default', - } -); - -export class IndexPatternListConfig { - public readonly key: IndexPatternType = IndexPatternType.DEFAULT; - - public getIndexPatternTags(indexPattern: IndexPattern, isDefault: boolean): IndexPatternTag[] { - return isDefault - ? [ - { - key: 'default', - name: defaultIndexPatternListName, - }, - ] - : []; - } - - public getFieldInfo(indexPattern: IndexPattern, field: IndexPatternField): string[] { - return []; - } - - public areScriptedFieldsEnabled(indexPattern: IndexPattern): boolean { - return true; - } -} diff --git a/src/plugins/index_pattern_management/public/service/list/index.ts b/src/plugins/index_pattern_management/public/service/list/index.ts deleted file mode 100644 index 738b807ac7624..0000000000000 --- a/src/plugins/index_pattern_management/public/service/list/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { IndexPatternListConfig } from './config'; -export { IndexPatternListManager } from './manager'; -// @ts-ignore -export { RollupIndexPatternListConfig } from './rollup_list_config'; diff --git a/src/plugins/index_pattern_management/public/service/list/manager.ts b/src/plugins/index_pattern_management/public/service/list/manager.ts deleted file mode 100644 index d9cefbd8001a5..0000000000000 --- a/src/plugins/index_pattern_management/public/service/list/manager.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { IndexPattern, IndexPatternField } from 'src/plugins/data/public'; -import { once } from 'lodash'; -import { CoreStart } from '../../../../../core/public'; -import { IndexPatternListConfig, IndexPatternTag } from './config'; -import { CONFIG_ROLLUPS } from '../../constants'; -import { RollupIndexPatternListConfig } from './rollup_list_config'; - -interface IndexPatternListManagerStart { - uiSettings: CoreStart['uiSettings']; -} - -export class IndexPatternListManager { - start({ uiSettings }: IndexPatternListManagerStart) { - const getConfigs = once(() => { - const configs: IndexPatternListConfig[] = []; - configs.push(new IndexPatternListConfig()); - - if (uiSettings.isDeclared(CONFIG_ROLLUPS) && uiSettings.get(CONFIG_ROLLUPS)) { - configs.push(new RollupIndexPatternListConfig()); - } - - return configs; - }); - return { - getIndexPatternTags: (indexPattern: IndexPattern, isDefault: boolean) => - getConfigs().reduce( - (tags: IndexPatternTag[], config) => - config.getIndexPatternTags - ? tags.concat(config.getIndexPatternTags(indexPattern, isDefault)) - : tags, - [] - ), - - getFieldInfo: (indexPattern: IndexPattern, field: IndexPatternField): string[] => - getConfigs().reduce( - (info: string[], config) => - config.getFieldInfo ? info.concat(config.getFieldInfo(indexPattern, field)) : info, - [] - ), - - areScriptedFieldsEnabled: (indexPattern: IndexPattern): boolean => - getConfigs().every((config) => - config.areScriptedFieldsEnabled ? config.areScriptedFieldsEnabled(indexPattern) : true - ), - }; - } -} diff --git a/src/plugins/index_pattern_management/public/service/list/rollup_list_config.ts b/src/plugins/index_pattern_management/public/service/list/rollup_list_config.ts deleted file mode 100644 index bb9da0d461701..0000000000000 --- a/src/plugins/index_pattern_management/public/service/list/rollup_list_config.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { IndexPattern, IndexPatternField, IndexPatternType } from '../../../../data/public'; -import { IndexPatternListConfig } from '.'; - -function isRollup(indexPattern: IndexPattern) { - return indexPattern.type === IndexPatternType.ROLLUP; -} - -export class RollupIndexPatternListConfig extends IndexPatternListConfig { - key = IndexPatternType.ROLLUP; - - getIndexPatternTags = (indexPattern: IndexPattern) => { - return isRollup(indexPattern) - ? [ - { - key: 'rollup', - name: 'Rollup', - }, - ] - : []; - }; - - getFieldInfo = (indexPattern: IndexPattern, field: IndexPatternField) => { - if (!isRollup(indexPattern)) { - return []; - } - - const allAggs = indexPattern.typeMeta && indexPattern.typeMeta.aggs; - const fieldAggs = allAggs && Object.keys(allAggs).filter((agg) => allAggs[agg][field.name]); - - if (!fieldAggs || !fieldAggs.length) { - return []; - } - - return ['Rollup aggregations:'].concat( - fieldAggs.map((aggName) => { - const agg = allAggs![aggName][field.name]; - switch (aggName) { - case 'date_histogram': - return `${aggName} (interval: ${agg.fixed_interval}, ${ - agg.delay ? `delay: ${agg.delay},` : '' - } ${agg.time_zone})`; - case 'histogram': - return `${aggName} (interval: ${agg.interval})`; - default: - return aggName; - } - }) - ); - }; - - areScriptedFieldsEnabled = (indexPattern: IndexPattern) => { - return !isRollup(indexPattern); - }; -} diff --git a/src/plugins/index_pattern_management/public/types.ts b/src/plugins/index_pattern_management/public/types.ts index a61eeb99b25a5..b2c77fccf90b9 100644 --- a/src/plugins/index_pattern_management/public/types.ts +++ b/src/plugins/index_pattern_management/public/types.ts @@ -20,6 +20,7 @@ import { ManagementAppMountParams } from '../../management/public'; import { IndexPatternManagementStart } from './index'; import { KibanaReactContextValue } from '../../kibana_react/public'; import { IndexPatternFieldEditorStart } from '../../index_pattern_field_editor/public'; +import { IndexPatternEditorStart } from '../../index_pattern_editor/public'; export interface IndexPatternManagmentContext { chrome: ChromeStart; @@ -34,6 +35,7 @@ export interface IndexPatternManagmentContext { indexPatternManagementStart: IndexPatternManagementStart; setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; fieldFormatEditors: IndexPatternFieldEditorStart['fieldFormatEditors']; + IndexPatternEditor: IndexPatternEditorStart['IndexPatternEditorComponent']; } export type IndexPatternManagmentContextValue = KibanaReactContextValue; diff --git a/src/plugins/index_pattern_management/tsconfig.json b/src/plugins/index_pattern_management/tsconfig.json index 37bd3e4aa5bbb..16afcb3599fec 100644 --- a/src/plugins/index_pattern_management/tsconfig.json +++ b/src/plugins/index_pattern_management/tsconfig.json @@ -20,5 +20,6 @@ { "path": "../kibana_utils/tsconfig.json" }, { "path": "../es_ui_shared/tsconfig.json" }, { "path": "../index_pattern_field_editor/tsconfig.json" }, + { "path": "../index_pattern_editor/tsconfig.json" }, ] } diff --git a/test/accessibility/apps/management.ts b/test/accessibility/apps/management.ts index 7a99e5832448f..538755b482fbf 100644 --- a/test/accessibility/apps/management.ts +++ b/test/accessibility/apps/management.ts @@ -70,6 +70,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.settings.clickAddNewIndexPatternButton(); await PageObjects.header.waitUntilLoadingHasFinished(); await a11y.testAppSnapshot(); + await testSubjects.click('closeFlyoutButton'); }); // We are navigating back to index pattern page to test field formatters diff --git a/test/functional/apps/management/_create_index_pattern_wizard.js b/test/functional/apps/management/_create_index_pattern_wizard.js index d4b49d74d1b90..b2f24e530cb12 100644 --- a/test/functional/apps/management/_create_index_pattern_wizard.js +++ b/test/functional/apps/management/_create_index_pattern_wizard.js @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -import expect from '@kbn/expect'; - export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); @@ -23,23 +21,6 @@ export default function ({ getService, getPageObjects }) { await PageObjects.settings.clickKibanaIndexPatterns(); }); - describe('step 1 next button', function () { - it('is disabled by default', async function () { - await (await testSubjects.find('createIndexPatternButton')).click(); - const btn = await PageObjects.settings.getCreateIndexPatternGoToStep2Button(); - const isEnabled = await btn.isEnabled(); - expect(isEnabled).not.to.be.ok(); - }); - - it('is enabled once an index pattern with matching indices has been entered', async function () { - await PageObjects.settings.setIndexPatternField(); - await PageObjects.common.sleep(1000); - const btn = await PageObjects.settings.getCreateIndexPatternGoToStep2Button(); - const isEnabled = await btn.isEnabled(); - expect(isEnabled).to.be.ok(); - }); - }); - describe('index alias', () => { before(async function () { await security.testUser.setRoles(['kibana_admin', 'test_alias1_reader']); diff --git a/test/functional/apps/management/_index_pattern_create_delete.js b/test/functional/apps/management/_index_pattern_create_delete.js index b195e4c8d5857..5be5cb3418bf8 100644 --- a/test/functional/apps/management/_index_pattern_create_delete.js +++ b/test/functional/apps/management/_index_pattern_create_delete.js @@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }) { const log = getService('log'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); + const find = getService('find'); const PageObjects = getPageObjects(['settings', 'common', 'header']); describe('creating and deleting default index', function describeIndexTests() { @@ -29,6 +30,30 @@ export default function ({ getService, getPageObjects }) { }); }); + describe('can open and close editor', function () { + it('without creating index pattern', async function () { + await PageObjects.settings.clickKibanaIndexPatterns(); + await PageObjects.settings.clickAddNewIndexPatternButton(); + await testSubjects.click('closeFlyoutButton'); + await testSubjects.find('createIndexPatternButton'); + }); + }); + + describe('validation', function () { + it('can display errors', async function () { + await PageObjects.settings.clickAddNewIndexPatternButton(); + await PageObjects.settings.setIndexPatternField('log*'); + await (await PageObjects.settings.getSaveIndexPatternButton()).click(); + await find.byClassName('euiFormErrorText'); + }); + + it('can resolve errors and submit', async function () { + await PageObjects.settings.selectTimeFieldOption('@timestamp'); + await (await PageObjects.settings.getSaveIndexPatternButton()).click(); + await PageObjects.settings.removeIndexPattern(); + }); + }); + describe('special character handling', () => { it('should handle special charaters in template input', async () => { await PageObjects.settings.clickAddNewIndexPatternButton(); diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index cb8f198177017..9cdd33ef768f9 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -43,7 +43,10 @@ export class SettingsPageObject extends FtrService { async clickKibanaIndexPatterns() { this.log.debug('clickKibanaIndexPatterns link'); - await this.testSubjects.click('indexPatterns'); + const currentUrl = await this.browser.getCurrentUrl(); + if (!currentUrl.endsWith('indexPatterns')) { + await this.testSubjects.click('indexPatterns'); + } await this.header.waitUntilLoadingHasFinished(); } @@ -122,38 +125,28 @@ export class SettingsPageObject extends FtrService { } async getIndexPatternField() { - return await this.testSubjects.find('createIndexPatternNameInput'); - } - - async clickTimeFieldNameField() { - return await this.testSubjects.click('createIndexPatternTimeFieldSelect'); + return this.testSubjects.find('createIndexPatternNameInput'); } async getTimeFieldNameField() { - return await this.testSubjects.find('createIndexPatternTimeFieldSelect'); + const wrapperElement = await this.testSubjects.find('timestampField'); + return wrapperElement.findByTestSubject('comboBoxSearchInput'); } async selectTimeFieldOption(selection: string) { // open dropdown - await this.clickTimeFieldNameField(); - // close dropdown, keep focus - await this.clickTimeFieldNameField(); - await this.header.waitUntilLoadingHasFinished(); - return await this.retry.try(async () => { - this.log.debug(`selectTimeFieldOption(${selection})`); - const timeFieldOption = await this.getTimeFieldOption(selection); - await timeFieldOption.click(); - const selected = await timeFieldOption.isSelected(); - if (!selected) throw new Error('option not selected: ' + selected); - }); + const timefield = await this.getTimeFieldNameField(); + await timefield.click(); + await this.browser.pressKeys(selection); + await this.browser.pressKeys(this.browser.keys.TAB); } async getTimeFieldOption(selection: string) { return await this.find.displayedByCssSelector('option[value="' + selection + '"]'); } - async getCreateIndexPatternButton() { - return await this.testSubjects.find('createIndexPatternButton'); + async getSaveIndexPatternButton() { + return await this.testSubjects.find('saveIndexPatternButton'); } async getCreateButton() { @@ -350,25 +343,19 @@ export class SettingsPageObject extends FtrService { await this.header.waitUntilLoadingHasFinished(); await this.clickAddNewIndexPatternButton(); + await this.header.waitUntilLoadingHasFinished(); if (!isStandardIndexPattern) { - await this.clickCreateNewRollupButton(); + await this.selectRollupIndexPatternType(); } - await this.header.waitUntilLoadingHasFinished(); await this.retry.try(async () => { await this.setIndexPatternField(indexPatternName); }); - const btn = await this.getCreateIndexPatternGoToStep2Button(); - await this.retry.waitFor(`index pattern Go To Step 2 button to be enabled`, async () => { - return await btn.isEnabled(); - }); - await btn.click(); - await this.common.sleep(2000); if (timefield) { await this.selectTimeFieldOption(timefield); } - await (await this.getCreateIndexPatternButton()).click(); + await (await this.getSaveIndexPatternButton()).click(); }); await this.header.waitUntilLoadingHasFinished(); await this.retry.try(async () => { @@ -381,16 +368,38 @@ export class SettingsPageObject extends FtrService { } }); + if (!isStandardIndexPattern) { + const badges = await this.find.allByCssSelector('.euiBadge__text'); + const text = await badges[1].getVisibleText(); + expect(text).to.equal('Rollup'); + } + return await this.getIndexPatternIdFromUrl(); } async clickAddNewIndexPatternButton() { await this.common.scrollKibanaBodyTop(); - await this.testSubjects.click('createIndexPatternButton'); + + // if flyout is open + const flyoutView = await this.testSubjects.exists('createIndexPatternButtonFlyout'); + if (flyoutView) { + await this.testSubjects.click('createIndexPatternButtonFlyout'); + return; + } + + const tableView = await this.testSubjects.exists('createIndexPatternButton'); + if (tableView) { + await this.testSubjects.click('createIndexPatternButton'); + } + const flyoutView2 = await this.testSubjects.exists('createIndexPatternButtonFlyout'); + if (flyoutView2) { + await this.testSubjects.click('createIndexPatternButtonFlyout'); + } } - async clickCreateNewRollupButton() { - await this.testSubjects.click('createRollupIndexPatternButton'); + async selectRollupIndexPatternType() { + await this.testSubjects.click('typeField'); + await this.testSubjects.click('rollupType'); } async getIndexPatternIdFromUrl() { diff --git a/tsconfig.json b/tsconfig.json index ab5a02702e611..3d6c29875c902 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -71,6 +71,7 @@ { "path": "./src/plugins/visualize/tsconfig.json" }, { "path": "./src/plugins/index_pattern_management/tsconfig.json" }, { "path": "./src/plugins/index_pattern_field_editor/tsconfig.json" }, + { "path": "./src/plugins/index_pattern_editor/tsconfig.json" }, { "path": "./x-pack/plugins/actions/tsconfig.json" }, { "path": "./x-pack/plugins/alerting/tsconfig.json" }, { "path": "./x-pack/plugins/apm/tsconfig.json" }, diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 1def88087ed86..1807a7014e389 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -57,6 +57,7 @@ { "path": "./src/plugins/visualizations/tsconfig.json" }, { "path": "./src/plugins/visualize/tsconfig.json" }, { "path": "./src/plugins/index_pattern_management/tsconfig.json" }, + { "path": "./src/plugins/index_pattern_editor/tsconfig.json" }, { "path": "./test/tsconfig.json" }, { "path": "./x-pack/plugins/actions/tsconfig.json" }, { "path": "./x-pack/plugins/alerting/tsconfig.json" }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d805f68db9870..646473260f013 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2894,62 +2894,8 @@ "indexPatternManagement.actions.createButton": "フィールドを作成", "indexPatternManagement.actions.deleteButton": "削除", "indexPatternManagement.actions.saveButton": "フィールドを保存", - "indexPatternManagement.aliasLabel": "エイリアス", "indexPatternManagement.createHeader": "スクリプトフィールドを作成", - "indexPatternManagement.createIndexPattern.betaLabel": "ベータ", - "indexPatternManagement.createIndexPattern.description": "インデックスパターンは、{single}または{multiple}データソース、{star}と一致します。", - "indexPatternManagement.createIndexPattern.documentation": "ドキュメンテーションを表示", - "indexPatternManagement.createIndexPattern.emptyState.checkDataButton": "新規データを確認", - "indexPatternManagement.createIndexPattern.emptyState.createAnyway": "一部のインデックスは表示されない場合があります。{link}してください。", - "indexPatternManagement.createIndexPattern.emptyState.createAnywayLink": "インデックスパターンを作成します", - "indexPatternManagement.createIndexPattern.emptyState.haveData": "すでにデータがある場合", - "indexPatternManagement.createIndexPattern.emptyState.integrationCardDescription": "さまざまなソースからデータを追加します。", - "indexPatternManagement.createIndexPattern.emptyState.integrationCardTitle": "統合の追加", - "indexPatternManagement.createIndexPattern.emptyState.learnMore": "詳細について", - "indexPatternManagement.createIndexPattern.emptyState.noDataTitle": "Kibanaを試しますか?まずデータが必要です。", - "indexPatternManagement.createIndexPattern.emptyState.readDocs": "ドキュメンテーションを表示", - "indexPatternManagement.createIndexPattern.emptyState.sampleDataCardDescription": "データセットとKibanaダッシュボードを読み込みます。", - "indexPatternManagement.createIndexPattern.emptyState.sampleDataCardTitle": "サンプルデータの追加", - "indexPatternManagement.createIndexPattern.emptyState.uploadCardDescription": "CSV、NDJSON、またはログファイルをインポートします。", - "indexPatternManagement.createIndexPattern.emptyState.uploadCardTitle": "ファイルをアップロード", - "indexPatternManagement.createIndexPattern.includeSystemIndicesToggleSwitchLabel": "システムと非表示のインデックスを含める", - "indexPatternManagement.createIndexPattern.loadClustersFailMsg": "リモートクラスターの読み込みに失敗", - "indexPatternManagement.createIndexPattern.loadIndicesFailMsg": "インデックスの読み込みに失敗", - "indexPatternManagement.createIndexPattern.loadingState.checkingLabel": "Elasticsearchデータを確認中", - "indexPatternManagement.createIndexPattern.step.indexPattern.allowLabel": "複数インデックスの一致にアスタリスク ({asterisk}) を使用。", - "indexPatternManagement.createIndexPattern.step.indexPattern.disallowLabel": "スペースと{characterList}は使用できません。", - "indexPatternManagement.createIndexPattern.step.indexPatternLabel": "インデックスパターン名", - "indexPatternManagement.createIndexPattern.step.indexPatternPlaceholder": "index-name-*", - "indexPatternManagement.createIndexPattern.step.invalidCharactersErrorMessage": "{indexPatternName}にはスペースや{characterList}は使えません。", - "indexPatternManagement.createIndexPattern.step.loadingHeader": "一致するインデックスを検索中…", - "indexPatternManagement.createIndexPattern.step.nextStepButton": "次のステップ", - "indexPatternManagement.createIndexPattern.step.pagingLabel": "ページごとの行数:{perPage}", - "indexPatternManagement.createIndexPattern.step.status.noSystemIndicesLabel": "パターンに一致するElasticsearchインデックスがありません。", - "indexPatternManagement.createIndexPattern.step.status.noSystemIndicesWithPromptLabel": "パターンに一致するElasticsearchインデックスがありません。一致するシステムインデックスを表示するには、上のスイッチを切り替えます。", - "indexPatternManagement.createIndexPattern.step.warningHeader": "すでに{query}という名前のインデックスパターンがあります。", - "indexPatternManagement.createIndexPattern.stepHeader": "ステップ1/2:インデックスパターンを定義", - "indexPatternManagement.createIndexPattern.stepTime.backButton": "戻る", - "indexPatternManagement.createIndexPattern.stepTime.createPatternButton": "インデックスパターンを作成", - "indexPatternManagement.createIndexPattern.stepTime.creatingLabel": "インデックスパターンを作成中…", - "indexPatternManagement.createIndexPattern.stepTime.error": "エラー", - "indexPatternManagement.createIndexPattern.stepTime.field.loadingDropDown": "読み込み中…", - "indexPatternManagement.createIndexPattern.stepTime.field.noTimeFieldsLabel": "このインデックスパターンに一致するインデックスには時間フィールドがありません。", - "indexPatternManagement.createIndexPattern.stepTime.fieldLabel": "時間フィールド", - "indexPatternManagement.createIndexPattern.stepTime.noTimeFieldOptionLabel": "時間フィルターを使用しない", - "indexPatternManagement.createIndexPattern.stepTime.noTimeFieldsLabel": "このインデックスパターンに一致するインデックスには時間フィールドがありません。", - "indexPatternManagement.createIndexPattern.stepTime.options.hideButton": "高度なSIEM設定の非表示化", - "indexPatternManagement.createIndexPattern.stepTime.options.patternHeader": "カスタムインデックスパターンID", - "indexPatternManagement.createIndexPattern.stepTime.options.patternLabel": "Kibanaはそれぞれのインデックスパターンに固有の識別子を割り当てます。固有のIDを使用しない場合は、カスタムIDを入力してください。", - "indexPatternManagement.createIndexPattern.stepTime.options.patternPlaceholder": "custom-index-pattern-id", - "indexPatternManagement.createIndexPattern.stepTime.options.showButton": "高度なSIEM設定の表示", - "indexPatternManagement.createIndexPattern.stepTime.patterAlreadyExists": "カスタムインデックスパターンIDがすでに存在します。", - "indexPatternManagement.createIndexPattern.stepTime.refreshButton": "更新", - "indexPatternManagement.createIndexPattern.stepTime.timeDescription": "グローバル時間フィルターで使用するためのプライマリ時間フィールドを選択してください。", - "indexPatternManagement.createIndexPattern.stepTimeHeader": "ステップ2/2:設定の構成", - "indexPatternManagement.createIndexPattern.stepTimeLabel": "{indexPattern} {indexPatternName}の設定を指定します。", - "indexPatternManagement.createIndexPatternHeader": "{indexPatternName}の作成", "indexPatternManagement.customLabel": "カスタムラベル", - "indexPatternManagement.dataStreamLabel": "データストリーム", "indexPatternManagement.defaultFormatDropDown": "- デフォルト -", "indexPatternManagement.defaultFormatHeader": "フォーマット (デフォルト:{defaultFormat}) ", "indexPatternManagement.deleteField.cancelButton": "キャンセル", @@ -2961,9 +2907,6 @@ "indexPatternManagement.disabledCallOutHeader": "スクリプティングが無効です", "indexPatternManagement.disabledCallOutLabel": "Elasticsearchでのすべてのインラインスクリプティングが無効になっています。Kibanaでスクリプトフィールドを使用するには、インラインスクリプティングを有効にする必要があります。", "indexPatternManagement.editHeader": "{fieldName}を編集", - "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全アグリゲーションを実行", - "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", - "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", "indexPatternManagement.editIndexPattern.deleteButton": "削除", "indexPatternManagement.editIndexPattern.deleteHeader": "インデックスパターンを削除しますか?", "indexPatternManagement.editIndexPattern.deprecation": "スクリプトフィールドは廃止予定です。代わりに{runtimeDocs}を使用してください。", @@ -3048,31 +2991,12 @@ "indexPatternManagement.editIndexPattern.timeFilterHeader": "時刻フィールド:「{timeFieldName}」", "indexPatternManagement.editIndexPattern.timeFilterLabel.mappingAPILink": "フィールドマッピング", "indexPatternManagement.editIndexPattern.timeFilterLabel.timeFilterDetail": "{indexPatternTitle}でフィールドを表示して編集します。型や検索可否などのフィールド属性はElasticsearchで{mappingAPILink}に基づきます。", - "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription": "要約データに制限された集約を実行します。", - "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText": "ロールアップインデックスパターン", - "indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName": "ロールアップインデックスパターン", - "indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel": "ロールアップ", - "indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError": "ロールアップインデックスパターンエラー:ロールアップインデックスの 1 つと一致している必要があります", - "indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError": "ロールアップインデックスパターンエラー:一致できるロールアップインデックスは 1 つだけです", - "indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError": "ロールアップインデックスパターンエラー:{error}", - "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "ロールアップインデックスパターンのKibanaのサポートはベータ版です。保存された検索、可視化、ダッシュボードでこれらのパターンを使用すると問題が発生する場合があります。Timelionや機械学習などの一部の高度な機能ではサポートされていません。", - "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "ロールアップインデックスパターンは、1つのロールアップインデックスとゼロ以上の標準インデックスと一致させることができます。ロールアップインデックスパターンでは、メトリック、フィールド、間隔、アグリゲーションが制限されています。ロールアップインデックスは、1つのジョブ構成があるインデックス、または複数のジョブと互換する構成があるインデックスに制限されています。", - "indexPatternManagement.emptyIndexPatternPrompt.documentation": "ドキュメンテーションを表示", - "indexPatternManagement.emptyIndexPatternPrompt.indexPatternExplanation": "Kibanaでは、検索するインデックスを特定するためにインデックスパターンが必要です。インデックスパターンは、昨日のログデータなど特定のインデックス、またはログデータを含むすべてのインデックスを参照できます。", - "indexPatternManagement.emptyIndexPatternPrompt.learnMore": "詳細について", - "indexPatternManagement.emptyIndexPatternPrompt.nowCreate": "インデックスパターンを作成します。", - "indexPatternManagement.emptyIndexPatternPrompt.youHaveData": "Elasticsearchにデータがあります。", "indexPatternManagement.fieldTypeConflict": "フィールドタイプの矛盾", "indexPatternManagement.formatHeader": "フォーマット", "indexPatternManagement.formatLabel": "フォーマットは、特定の値の表示形式を管理できます。また、値を完全に変更したり、Discover でのハイライト機能を無効にしたりすることも可能です。", - "indexPatternManagement.frozenLabel": "凍結", "indexPatternManagement.header.runtimeLink": "ランタイムフィールド", - "indexPatternManagement.indexLabel": "インデックス", "indexPatternManagement.indexNameLabel": "インデックス名", - "indexPatternManagement.indexPattern.goToPatternButtonLabel": "既存のパターンに移動", "indexPatternManagement.indexPattern.sectionsHeader": "インデックスパターン", - "indexPatternManagement.indexPattern.titleExistsLabel": "「{title}」というタイトルのインデックスパターンがすでに存在します。", - "indexPatternManagement.indexPatternList.createButton.betaLabel": "ベータ", "indexPatternManagement.indexPatterns.badge.readOnly.text": "読み取り専用", "indexPatternManagement.indexPatterns.badge.readOnly.tooltip": "インデックスパターンを保存できません", "indexPatternManagement.indexPatterns.createBreadcrumb": "インデックスパターンを作成", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1f13b453c2c6f..65e6728e6ca13 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2906,68 +2906,8 @@ "indexPatternManagement.actions.createButton": "创建字段", "indexPatternManagement.actions.deleteButton": "删除", "indexPatternManagement.actions.saveButton": "保存字段", - "indexPatternManagement.aliasLabel": "别名", "indexPatternManagement.createHeader": "创建脚本字段", - "indexPatternManagement.createIndexPattern.betaLabel": "公测版", - "indexPatternManagement.createIndexPattern.description": "索引模式可以匹配单个源,例如 {single} 或 {multiple} 个数据源、{star}。", - "indexPatternManagement.createIndexPattern.documentation": "阅读文档", - "indexPatternManagement.createIndexPattern.emptyState.checkDataButton": "检查新数据", - "indexPatternManagement.createIndexPattern.emptyState.createAnyway": "部分索引可能已隐藏。仍然尝试{link}。", - "indexPatternManagement.createIndexPattern.emptyState.createAnywayLink": "创建索引模式", - "indexPatternManagement.createIndexPattern.emptyState.haveData": "假设您已有数据?", - "indexPatternManagement.createIndexPattern.emptyState.integrationCardDescription": "从各种源添加数据。", - "indexPatternManagement.createIndexPattern.emptyState.integrationCardTitle": "添加集成", - "indexPatternManagement.createIndexPattern.emptyState.learnMore": "希望了解详情?", - "indexPatternManagement.createIndexPattern.emptyState.noDataTitle": "准备试用 Kibana?首先,您需要数据。", - "indexPatternManagement.createIndexPattern.emptyState.readDocs": "阅读文档", - "indexPatternManagement.createIndexPattern.emptyState.sampleDataCardDescription": "加载数据集和 Kibana 仪表板。", - "indexPatternManagement.createIndexPattern.emptyState.sampleDataCardTitle": "添加样例数据", - "indexPatternManagement.createIndexPattern.emptyState.uploadCardDescription": "导入 CSV、NDJSON 或日志文件。", - "indexPatternManagement.createIndexPattern.emptyState.uploadCardTitle": "上传文件", - "indexPatternManagement.createIndexPattern.includeSystemIndicesToggleSwitchLabel": "包括系统和隐藏索引", - "indexPatternManagement.createIndexPattern.loadClustersFailMsg": "无法加载远程集群", - "indexPatternManagement.createIndexPattern.loadIndicesFailMsg": "无法加载索引", - "indexPatternManagement.createIndexPattern.loadingState.checkingLabel": "正在检查 Elasticsearch 数据", - "indexPatternManagement.createIndexPattern.step.indexPattern.allowLabel": "使用星号 ({asterisk}) 匹配多个索引。", - "indexPatternManagement.createIndexPattern.step.indexPattern.disallowLabel": "不允许使用空格和字符 {characterList}。", - "indexPatternManagement.createIndexPattern.step.indexPatternLabel": "索引模式名称", - "indexPatternManagement.createIndexPattern.step.indexPatternPlaceholder": "index-name-*", - "indexPatternManagement.createIndexPattern.step.invalidCharactersErrorMessage": "{indexPatternName} 不能包含空格或字符:{characterList}", - "indexPatternManagement.createIndexPattern.step.loadingHeader": "正在寻找匹配的索引......", - "indexPatternManagement.createIndexPattern.step.nextStepButton": "下一步", - "indexPatternManagement.createIndexPattern.step.pagingLabel": "每页行数:{perPage}", - "indexPatternManagement.createIndexPattern.step.status.matchAnyLabel.matchAnyDetail": "您的索引模式可以匹配{sourceCount, plural, one {您的 # 个源} other {您的 # 个源中的任何一个} }。", - "indexPatternManagement.createIndexPattern.step.status.noSystemIndicesLabel": "没有 Elasticsearch 索引匹配您的模式。", - "indexPatternManagement.createIndexPattern.step.status.noSystemIndicesWithPromptLabel": "没有 Elasticsearch 索引匹配您的模式。要查看匹配的系统索引,请切换上面的开关。", - "indexPatternManagement.createIndexPattern.step.status.notMatchLabel.allIndicesLabel": "{indicesLength, plural, other {# 个索引} }", - "indexPatternManagement.createIndexPattern.step.status.notMatchLabel.notMatchDetail": "输入的索引模式不匹配任何索引。您可以在下面匹配您的 {strongIndices}{indicesLength, plural, one {} other {中任何一个} }。", - "indexPatternManagement.createIndexPattern.step.status.partialMatchLabel.partialMatchDetail": "您的索引模式不匹配任何索引,但您的 {strongIndices}{matchedIndicesLength, plural, other {看起来} }类似。", - "indexPatternManagement.createIndexPattern.step.status.partialMatchLabel.strongIndicesLabel": "{matchedIndicesLength, plural,one {索引} other {# 个索引} }", - "indexPatternManagement.createIndexPattern.step.status.successLabel.successDetail": "您的索引模式匹配 {sourceCount} 个{sourceCount, plural, other {源} }。", - "indexPatternManagement.createIndexPattern.step.warningHeader": "已有称作“{query}”的索引模式", - "indexPatternManagement.createIndexPattern.stepHeader": "第 1 步(共 2 步):定义索引模式", - "indexPatternManagement.createIndexPattern.stepTime.backButton": "返回", - "indexPatternManagement.createIndexPattern.stepTime.createPatternButton": "创建索引模式", - "indexPatternManagement.createIndexPattern.stepTime.creatingLabel": "正在创建索引模式……", - "indexPatternManagement.createIndexPattern.stepTime.error": "错误", - "indexPatternManagement.createIndexPattern.stepTime.field.loadingDropDown": "正在加载……", - "indexPatternManagement.createIndexPattern.stepTime.field.noTimeFieldsLabel": "匹配此索引模式的索引不包含任何时间字段。", - "indexPatternManagement.createIndexPattern.stepTime.fieldLabel": "时间字段", - "indexPatternManagement.createIndexPattern.stepTime.noTimeFieldOptionLabel": "我不想使用时间筛选", - "indexPatternManagement.createIndexPattern.stepTime.noTimeFieldsLabel": "匹配此索引模式的索引不包含任何时间字段。", - "indexPatternManagement.createIndexPattern.stepTime.options.hideButton": "隐藏高级设置", - "indexPatternManagement.createIndexPattern.stepTime.options.patternHeader": "定制索引模式 ID", - "indexPatternManagement.createIndexPattern.stepTime.options.patternLabel": "Kibana 将为每个索引模式提供唯一的标识符。如果不想使用此唯一 ID,请输入定制 ID。", - "indexPatternManagement.createIndexPattern.stepTime.options.patternPlaceholder": "custom-index-pattern-id", - "indexPatternManagement.createIndexPattern.stepTime.options.showButton": "显示高级设置", - "indexPatternManagement.createIndexPattern.stepTime.patterAlreadyExists": "自定义索引模式 ID 已存在。", - "indexPatternManagement.createIndexPattern.stepTime.refreshButton": "刷新", - "indexPatternManagement.createIndexPattern.stepTime.timeDescription": "选择用于全局时间筛选的主要时间字段。", - "indexPatternManagement.createIndexPattern.stepTimeHeader": "第 2 步(共 2 步):配置设置", - "indexPatternManagement.createIndexPattern.stepTimeLabel": "为您的 {indexPattern} {indexPatternName} 指定设置。", - "indexPatternManagement.createIndexPatternHeader": "创建 {indexPatternName}", "indexPatternManagement.customLabel": "定制标签", - "indexPatternManagement.dataStreamLabel": "数据流", "indexPatternManagement.defaultFormatDropDown": "- 默认值 -", "indexPatternManagement.defaultFormatHeader": "格式(默认值:{defaultFormat})", "indexPatternManagement.deleteField.cancelButton": "取消", @@ -2979,9 +2919,6 @@ "indexPatternManagement.disabledCallOutHeader": "脚本已禁用", "indexPatternManagement.disabledCallOutLabel": "所有内联脚本在 Elasticsearch 中已禁用。必须至少为一种语言启用内联脚本,才能在 Kibana 中使用脚本字段。", "indexPatternManagement.editHeader": "编辑 {fieldName}", - "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", - "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", - "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "索引模式", "indexPatternManagement.editIndexPattern.deleteButton": "删除", "indexPatternManagement.editIndexPattern.deleteHeader": "删除索引模式?", "indexPatternManagement.editIndexPattern.deprecation": "脚本字段已弃用。改用 {runtimeDocs}。", @@ -3067,31 +3004,12 @@ "indexPatternManagement.editIndexPattern.timeFilterHeader": "时间字段:“{timeFieldName}”", "indexPatternManagement.editIndexPattern.timeFilterLabel.mappingAPILink": "字段映射", "indexPatternManagement.editIndexPattern.timeFilterLabel.timeFilterDetail": "查看和编辑 {indexPatternTitle} 中的字段。字段属性,如类型和可搜索性,基于 Elasticsearch 中的 {mappingAPILink}。", - "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription": "针对汇总数据执行有限聚合", - "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText": "汇总/打包索引模式", - "indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName": "汇总/打包索引模式", - "indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel": "汇总/打包", - "indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError": "汇总/打包索引模式错误:必须匹配一个汇总/打包索引", - "indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError": "汇总/打包索引模式错误:只能匹配一个汇总/打包索引", - "indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError": "汇总索引模式错误:{error}", - "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "Kibana 对汇总/打包索引模式的支持处于公测版状态。将这些模式用于已保存搜索、可视化以及仪表板可能会遇到问题。某些高级功能,如 Timelion 和 Machine Learning,不支持这些模式。", - "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "可以根据一个汇总/打包索引和零个或更多常规索引匹配汇总/打包索引模式。汇总/打包索引模式的指标、字段、时间间隔和聚合有限。汇总/打包索引仅限于具有一个作业配置或多个作业配置兼容的索引。", - "indexPatternManagement.emptyIndexPatternPrompt.documentation": "阅读文档", - "indexPatternManagement.emptyIndexPatternPrompt.indexPatternExplanation": "Kibana 需要索引模式,以识别您要浏览的索引。索引模式可以指向特定索引(例如昨天的日志数据),或包含日志数据的所有索引。", - "indexPatternManagement.emptyIndexPatternPrompt.learnMore": "希望了解详情?", - "indexPatternManagement.emptyIndexPatternPrompt.nowCreate": "现在,创建索引模式。", - "indexPatternManagement.emptyIndexPatternPrompt.youHaveData": "您在 Elasticsearch 中有数据。", "indexPatternManagement.fieldTypeConflict": "字段类型冲突", "indexPatternManagement.formatHeader": "格式", "indexPatternManagement.formatLabel": "设置格式允许您控制特定值的显示方式。其还会导致值完全更改,并阻止 Discover 中的突出显示起作用。", - "indexPatternManagement.frozenLabel": "已冻结", "indexPatternManagement.header.runtimeLink": "运行时字段", - "indexPatternManagement.indexLabel": "索引", "indexPatternManagement.indexNameLabel": "索引名称", - "indexPatternManagement.indexPattern.goToPatternButtonLabel": "前往现有模式", "indexPatternManagement.indexPattern.sectionsHeader": "索引模式", - "indexPatternManagement.indexPattern.titleExistsLabel": "具有标题“{title}”的索引模式已存在。", - "indexPatternManagement.indexPatternList.createButton.betaLabel": "公测版", "indexPatternManagement.indexPatterns.badge.readOnly.text": "只读", "indexPatternManagement.indexPatterns.badge.readOnly.tooltip": "无法保存索引模式", "indexPatternManagement.indexPatterns.createBreadcrumb": "创建索引模式", diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index 52fcac769955c..c1610ebe0709f 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -133,7 +133,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`index pattern listing doesn't show create button`, async () => { await PageObjects.settings.clickKibanaIndexPatterns(); await testSubjects.existOrFail('emptyIndexPatternPrompt'); - await testSubjects.missingOrFail('createIndexPatternButton'); + await testSubjects.missingOrFail('createIndexPatternButtonFlyout'); }); it(`shows read-only badge`, async () => { diff --git a/x-pack/test/functional/apps/management/create_index_pattern_wizard.js b/x-pack/test/functional/apps/management/create_index_pattern_wizard.js index 246256cb4c2a1..445e340d236e6 100644 --- a/x-pack/test/functional/apps/management/create_index_pattern_wizard.js +++ b/x-pack/test/functional/apps/management/create_index_pattern_wizard.js @@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }) { method: 'PUT', }); - await PageObjects.settings.createIndexPattern('test_data_stream', false); + await PageObjects.settings.createIndexPattern('test_data_stream'); await es.transport.request({ path: '/_data_stream/test_data_stream', diff --git a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js index 1986efe202224..e479c6729d8c3 100644 --- a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js +++ b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js @@ -12,7 +12,6 @@ import mockRolledUpData, { mockIndices } from './hybrid_index_helper'; export default function ({ getService, getPageObjects }) { const es = getService('es'); const esArchiver = getService('esArchiver'); - const find = getService('find'); const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'settings']); const esDeleteAllIndices = getService('esDeleteAllIndices'); @@ -91,10 +90,6 @@ export default function ({ getService, getPageObjects }) { ); expect(filteredIndexPatternNames.length).to.be(1); - // make sure there are no toasts which might be showing unexpected errors - const toastShown = await find.existsByCssSelector('.euiToast'); - expect(toastShown).to.be(false); - // ensure all fields are available await PageObjects.settings.clickIndexPatternByName(rollupIndexPatternName); const fields = await PageObjects.settings.getFieldNames();