From e751b6fe1318216d4f08b498177f34400177a3ff Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 20 Apr 2021 22:17:22 +0100 Subject: [PATCH] [ML] Moving file data vizualizer to its own plugin (#96408) * [ML] Moving file data vizualizer to file upload plugin * removing maps plug dependency * fixing imports * small refactor * adding missing endpoints * fixing translations * fxing table controls * fixing types and disabling geo point test * actually disabling geo point test * making endpoints internal * moving UI code to separate plugin * enabling maps integration * cleaning up dependencies * fixing translation ids * moving analyze file endpoint out of file upload plugin * fixing transtations issues * refactor for lazy loading of component * updating limits * updating plugin asciidoc * code clean up * further clean up * adding comment * fixing really obvious CI error * removing commented out include * reenabling geo point test * fixing incorrectly changed import * removing ml from labels and identifiers * renaming function * moving analyse file endpoint to file upload plugin * reverting import path changes * adding esUiShared back in * fixing navigation tabs alignment in basic license * adding key to tab wrapper * reverting test label * further removal of ml references * removing ml label from more identifiers * fixing tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/developer/plugin-list.asciidoc | 4 + packages/kbn-optimizer/limits.yml | 1 + x-pack/.i18nrc.json | 1 + .../file_data_visualizer/common/constants.ts | 31 ++ .../common}/index.ts | 3 +- .../file_data_visualizer/common/types.ts | 22 ++ .../file_data_visualizer/jest.config.js | 12 + .../plugins/file_data_visualizer/kibana.json | 27 ++ .../file_data_visualizer/public/api/index.ts | 14 + .../public/application}/_index.scss | 0 .../application}/components/_index.scss | 0 .../components/about_panel/_about_panel.scss | 0 .../components/about_panel/_index.scss | 0 .../components/about_panel/about_panel.tsx | 4 +- .../components/about_panel/index.ts | 0 .../about_panel/welcome_content.tsx | 34 +-- .../analysis_summary/_analysis_summary.scss | 0 .../components/analysis_summary/_index.scss | 0 .../analysis_summary/analysis_summary.tsx | 18 +- .../components/analysis_summary/index.ts | 0 .../components/bottom_bar/bottom_bar.tsx | 10 +- .../components/bottom_bar/index.ts | 0 .../combined_fields/combined_field_label.tsx | 0 .../combined_fields/combined_fields_form.tsx | 16 +- .../combined_fields_read_only_form.tsx | 4 +- .../components/combined_fields/geo_point.tsx | 12 +- .../components/combined_fields/index.ts | 0 .../components/combined_fields/types.ts | 0 .../components/combined_fields/utils.test.ts | 0 .../components/combined_fields/utils.ts | 4 +- .../__snapshots__/overrides.test.js.snap | 8 +- .../components/edit_flyout/_edit_flyout.scss | 0 .../components/edit_flyout/_index.scss | 0 .../components/edit_flyout/edit_flyout.js | 6 +- .../components/edit_flyout/index.js | 0 .../components/edit_flyout/options/index.js | 0 .../edit_flyout/options/option_lists.js | 0 .../components/edit_flyout/options/options.js | 0 .../components/edit_flyout/overrides.js | 39 ++- .../components/edit_flyout/overrides.test.js | 2 +- .../edit_flyout/overrides_validation.js | 11 +- .../embedded_map/_embedded_map.scss | 8 + .../components/embedded_map/_index.scss | 1 + .../components/embedded_map/embedded_map.tsx | 155 ++++++++++ .../components/embedded_map/index.ts | 8 + .../examples_list/examples_list.tsx | 59 ++++ .../components/examples_list/index.ts | 8 + .../expanded_row/file_based_expanded_row.tsx | 22 +- .../geo_point_content/format_utils.ts | 2 +- .../geo_point_content/geo_point_content.tsx | 16 +- .../expanded_row/geo_point_content/index.ts | 0 .../components/expanded_row/index.ts | 0 .../_experimental_badge.scss | 2 +- .../components/experimental_badge/_index.scss | 0 .../experimental_badge/experimental_badge.tsx | 4 +- .../components/experimental_badge/index.ts | 0 .../explanation_flyout/explanation_flyout.tsx | 8 +- .../components/explanation_flyout/index.ts | 0 .../components/field_data_row/index.ts | 0 .../field_data_row/number_content_preview.tsx | 8 +- .../field_names_filter/field_names_filter.tsx | 6 +- .../components/field_names_filter/index.ts | 0 .../field_type_icon.test.tsx.snap | 16 + .../field_type_icon/field_type_icon.test.tsx | 52 ++++ .../field_type_icon/field_type_icon.tsx | 130 ++++++++ .../components/field_type_icon/index.ts | 8 + .../field_types_filter/field_types_filter.tsx | 26 +- .../components/field_types_filter/index.ts | 0 .../fields_stats_grid/create_fields.ts | 26 +- .../fields_stats_grid/fields_stats_grid.tsx | 32 +- .../fields_stats_grid/filter_fields.ts | 6 +- .../fields_stats_grid/get_field_names.ts | 16 +- .../components/fields_stats_grid/index.ts | 0 .../file_contents/_file_contents.scss | 0 .../components/file_contents/_index.scss | 0 .../file_contents/file_contents.tsx | 14 +- .../components/file_contents/index.ts | 0 .../_file_datavisualizer_view.scss | 0 .../file_datavisualizer_view/_index.scss | 0 .../file_datavisualizer_view/constants.ts | 0 .../file_datavisualizer_view.js | 15 +- .../file_error_callouts.tsx | 24 +- .../file_datavisualizer_view/index.js | 0 .../filebeat_config_flyout/filebeat_config.ts | 4 +- .../filebeat_config_flyout.tsx | 24 +- .../filebeat_config_flyout/index.ts | 0 .../components/import_errors/errors.tsx | 20 +- .../components/import_errors/index.ts | 0 .../import_progress/import_progress.tsx | 47 ++- .../components/import_progress/index.ts | 0 .../components/import_settings/advanced.tsx | 32 +- .../import_settings/import_settings.tsx | 6 +- .../components/import_settings/index.ts | 0 .../components/import_settings/simple.tsx | 8 +- .../import_summary/_import_sumary.scss | 0 .../components/import_summary/_index.scss | 0 .../components/import_summary/failures.tsx | 2 +- .../import_summary/import_summary.tsx | 16 +- .../components/import_summary/index.ts | 0 .../components/import_view/import_view.js | 56 ++-- .../components/import_view/index.js | 0 .../components/json_editor/index.ts | 8 + .../components/json_editor/json_editor.tsx | 58 ++++ .../components/multi_select_picker/index.ts | 8 + .../multi_select_picker.tsx | 145 +++++++++ .../components/results_links/index.ts | 0 .../results_links/results_links.tsx | 126 ++------ .../components/results_view/_index.scss | 0 .../results_view/_results_view.scss | 0 .../components/results_view/index.ts | 0 .../components/results_view/results_view.tsx | 8 +- .../stats_table/_field_data_row.scss | 86 ++++++ .../components/stats_table/_index.scss | 56 ++++ .../expanded_row_field_header.tsx | 15 + .../expanded_row_field_header/index.ts | 8 + .../components/field_count_stats/_index.scss | 3 + .../components/field_count_stats/index.ts | 13 + .../field_count_stats/metric_fields_count.tsx | 68 +++++ .../field_count_stats/total_fields_count.tsx | 67 +++++ .../field_data_expanded_row/_index.scss | 7 + .../_number_content.scss | 4 + .../boolean_content.tsx | 145 +++++++++ .../field_data_expanded_row/date_content.tsx | 90 ++++++ .../document_stats.tsx | 93 ++++++ .../expanded_row_content.tsx | 25 ++ .../field_data_expanded_row/index.ts | 15 + .../field_data_expanded_row/ip_content.tsx | 27 ++ .../keyword_content.tsx | 25 ++ .../number_content.tsx | 154 ++++++++++ .../field_data_expanded_row/other_content.tsx | 28 ++ .../field_data_expanded_row/text_content.tsx | 69 +++++ .../components/field_data_row/_index.scss | 3 + .../boolean_content_preview.tsx | 43 +++ .../field_data_row/column_chart.scss | 32 ++ .../field_data_row/column_chart.tsx | 84 ++++++ .../field_data_row/distinct_values.tsx | 24 ++ .../field_data_row/document_stats.tsx | 33 ++ .../field_data_row/field_histograms.ts | 68 +++++ .../components/field_data_row/index.ts | 8 + .../field_data_row/number_content_preview.tsx | 78 +++++ .../field_data_row/top_values_preview.tsx | 44 +++ .../field_data_row/use_column_chart.test.tsx | 177 +++++++++++ .../field_data_row/use_column_chart.tsx | 206 +++++++++++++ .../metric_distribution_chart/index.ts | 9 + .../metric_distribution_chart.tsx | 108 +++++++ ...metric_distribution_chart_data_builder.tsx | 156 ++++++++++ ...tric_distribution_chart_tooltip_header.tsx | 54 ++++ .../data_visualizer_stats_table.tsx | 284 ++++++++++++++++++ .../stats_table/hooks/color_range_legend.tsx | 146 +++++++++ .../components/stats_table/hooks/index.ts | 8 + .../stats_table/hooks/use_color_range.test.ts | 58 ++++ .../stats_table/hooks/use_color_range.ts | 219 ++++++++++++++ .../hooks/use_data_viz_chart_theme.ts | 55 ++++ .../components/stats_table/index.ts | 8 + .../stats_table/types/field_data_row.ts | 12 + .../stats_table/types/field_vis_config.ts | 98 ++++++ .../components/stats_table/types/index.ts | 16 + .../stats_table/use_table_settings.ts | 63 ++++ .../components/stats_table/utils.ts | 38 +++ .../components/top_values/_top_values.scss | 19 ++ .../components/top_values/index.ts | 8 + .../components/top_values/top_values.tsx | 115 +++++++ .../components/utils/format_value.ts | 88 ++++++ .../application/components/utils/index.ts | 12 + .../components/utils/kibana_field_format.ts | 19 ++ .../utils/number_as_ordinal.test.ts | 29 ++ .../components/utils/number_as_ordinal.ts | 20 ++ .../utils/round_to_decimal_place.test.ts | 38 +++ .../utils/round_to_decimal_place.ts | 20 ++ .../application}/components/utils/utils.ts | 3 +- .../application/file_datavisualizer.tsx | 30 ++ .../public/application/index.ts | 8 + .../public/application/kibana_context.ts | 13 + .../public/application/shared_imports.ts | 12 + .../util/field_types_utils.test.ts | 29 ++ .../application/util/field_types_utils.ts | 49 +++ .../public/application/util/get_max_bytes.ts | 14 + .../file_data_visualizer/public/index.ts | 14 + .../public/kibana_services.ts | 19 ++ .../public/lazy_load_bundle/index.ts | 33 ++ .../public/lazy_load_bundle/lazy/index.ts | 8 + .../file_data_visualizer/public/plugin.ts | 49 +++ .../file_data_visualizer/server/index.ts | 10 + .../file_data_visualizer/server/plugin.ts | 13 + .../file_data_visualizer/tsconfig.json | 20 ++ .../plugins/file_upload/common/constants.ts | 2 +- x-pack/plugins/file_upload/common/types.ts | 28 +- x-pack/plugins/file_upload/kibana.json | 16 +- .../plugins/file_upload/public/api/index.ts | 70 ++++- .../geojson_file_picker.tsx | 2 +- .../public/{ => importer}/get_max_bytes.ts | 5 +- .../file_upload/public/importer/importer.ts | 2 +- .../public/importer/validate_file.ts | 2 +- x-pack/plugins/file_upload/public/index.ts | 2 - .../public/lazy_load_bundle/index.ts | 2 +- x-pack/plugins/file_upload/public/plugin.ts | 8 +- .../server/get_time_field_range.ts | 54 ++++ x-pack/plugins/file_upload/server/routes.ts | 99 +++++- x-pack/plugins/file_upload/tsconfig.json | 1 + x-pack/plugins/ml/kibana.json | 2 +- .../ml/public/__mocks__/ml_start_deps.ts | 2 +- x-pack/plugins/ml/public/application/app.tsx | 4 +- .../components/navigation_menu/main_tabs.tsx | 18 +- .../contexts/kibana/kibana_context.ts | 4 +- .../application/datavisualizer/_index.scss | 1 - .../datavisualizer_selector.tsx | 8 +- .../fields_stats/_field_stats_card.scss | 150 --------- .../fields_stats/_fields_stats.scss | 6 - .../file_based/file_datavisualizer.tsx | 30 +- .../file_based/{index.ts => index.tsx} | 0 .../search_panel/field_type_filter.tsx | 2 +- .../field_data_expanded_row/index.ts | 1 - .../data_visualizer_stats_table.tsx | 3 - .../routes/datavisualizer/file_based.tsx | 5 +- .../application/util/dependency_cache.ts | 16 +- x-pack/plugins/ml/public/plugin.ts | 6 +- x-pack/plugins/ml/tsconfig.json | 2 +- .../translations/translations/ja-JP.json | 159 ---------- .../translations/translations/zh-CN.json | 161 ---------- 219 files changed, 5249 insertions(+), 969 deletions(-) create mode 100644 x-pack/plugins/file_data_visualizer/common/constants.ts rename x-pack/plugins/{ml/public/application/datavisualizer/file_based/components/utils => file_data_visualizer/common}/index.ts (72%) create mode 100644 x-pack/plugins/file_data_visualizer/common/types.ts create mode 100644 x-pack/plugins/file_data_visualizer/jest.config.js create mode 100644 x-pack/plugins/file_data_visualizer/kibana.json create mode 100644 x-pack/plugins/file_data_visualizer/public/api/index.ts rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/about_panel/_about_panel.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/about_panel/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/about_panel/about_panel.tsx (93%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/about_panel/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/about_panel/welcome_content.tsx (78%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/analysis_summary/_analysis_summary.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/analysis_summary/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/analysis_summary/analysis_summary.tsx (80%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/analysis_summary/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/bottom_bar/bottom_bar.tsx (86%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/bottom_bar/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/combined_fields/combined_field_label.tsx (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/combined_fields/combined_fields_form.tsx (89%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/combined_fields/combined_fields_read_only_form.tsx (83%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/combined_fields/geo_point.tsx (90%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/combined_fields/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/combined_fields/types.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/combined_fields/utils.test.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/combined_fields/utils.ts (97%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/__snapshots__/overrides.test.js.snap (96%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/_edit_flyout.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/edit_flyout.js (91%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/index.js (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/options/index.js (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/options/option_lists.js (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/options/options.js (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/overrides.js (90%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/overrides.test.js (94%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/edit_flyout/overrides_validation.js (84%) create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/_embedded_map.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/_index.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/embedded_map.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/examples_list/examples_list.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/examples_list/index.ts rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/expanded_row/file_based_expanded_row.tsx (69%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/expanded_row/geo_point_content/format_utils.ts (96%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/expanded_row/geo_point_content/geo_point_content.tsx (78%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/expanded_row/geo_point_content/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/expanded_row/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/experimental_badge/_experimental_badge.scss (74%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/experimental_badge/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/experimental_badge/experimental_badge.tsx (85%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/experimental_badge/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/explanation_flyout/explanation_flyout.tsx (87%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/explanation_flyout/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/field_data_row/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/field_data_row/number_content_preview.tsx (84%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/field_names_filter/field_names_filter.tsx (84%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/field_names_filter/index.ts (100%) create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/field_type_icon.test.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/field_type_icon.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/index.ts rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/field_types_filter/field_types_filter.tsx (65%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/field_types_filter/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/fields_stats_grid/create_fields.ts (80%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/fields_stats_grid/fields_stats_grid.tsx (79%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/fields_stats_grid/filter_fields.ts (81%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/fields_stats_grid/get_field_names.ts (75%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/fields_stats_grid/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_contents/_file_contents.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_contents/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_contents/file_contents.tsx (78%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_contents/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_datavisualizer_view/_file_datavisualizer_view.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_datavisualizer_view/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_datavisualizer_view/constants.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_datavisualizer_view/file_datavisualizer_view.js (95%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_datavisualizer_view/file_error_callouts.tsx (79%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/file_datavisualizer_view/index.js (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/filebeat_config_flyout/filebeat_config.ts (91%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/filebeat_config_flyout/filebeat_config_flyout.tsx (83%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/filebeat_config_flyout/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_errors/errors.tsx (81%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_errors/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_progress/import_progress.tsx (81%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_progress/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_settings/advanced.tsx (84%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_settings/import_settings.tsx (92%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_settings/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_settings/simple.tsx (86%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_summary/_import_sumary.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_summary/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_summary/failures.tsx (95%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_summary/import_summary.tsx (84%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_summary/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_view/import_view.js (92%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/import_view/index.js (100%) create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/json_editor/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/json_editor/json_editor.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/multi_select_picker/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/multi_select_picker/multi_select_picker.tsx rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/results_links/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/results_links/results_links.tsx (61%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/results_view/_index.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/results_view/_results_view.scss (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/results_view/index.ts (100%) rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/results_view/results_view.tsx (89%) create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/_field_data_row.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/_index.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/expanded_row_field_header/expanded_row_field_header.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/expanded_row_field_header/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/_index.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/metric_fields_count.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/total_fields_count.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/_index.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/_number_content.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/boolean_content.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/date_content.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/document_stats.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/expanded_row_content.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/ip_content.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/keyword_content.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/number_content.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/other_content.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/text_content.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/_index.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/boolean_content_preview.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/column_chart.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/column_chart.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/distinct_values.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/document_stats.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/field_histograms.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/number_content_preview.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/top_values_preview.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/use_column_chart.test.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/use_column_chart.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart_data_builder.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart_tooltip_header.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/data_visualizer_stats_table.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/color_range_legend.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_color_range.test.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_color_range.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_data_viz_chart_theme.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/field_data_row.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/field_vis_config.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/use_table_settings.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/stats_table/utils.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/top_values/_top_values.scss create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/top_values/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/top_values/top_values.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/utils/format_value.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/utils/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/utils/kibana_field_format.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/utils/number_as_ordinal.test.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/utils/number_as_ordinal.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/utils/round_to_decimal_place.test.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/components/utils/round_to_decimal_place.ts rename x-pack/plugins/{ml/public/application/datavisualizer/file_based => file_data_visualizer/public/application}/components/utils/utils.ts (96%) create mode 100644 x-pack/plugins/file_data_visualizer/public/application/file_datavisualizer.tsx create mode 100644 x-pack/plugins/file_data_visualizer/public/application/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/kibana_context.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/shared_imports.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/util/field_types_utils.test.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/util/field_types_utils.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/application/util/get_max_bytes.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/kibana_services.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/lazy/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/public/plugin.ts create mode 100644 x-pack/plugins/file_data_visualizer/server/index.ts create mode 100644 x-pack/plugins/file_data_visualizer/server/plugin.ts create mode 100644 x-pack/plugins/file_data_visualizer/tsconfig.json rename x-pack/plugins/file_upload/public/{ => importer}/get_max_bytes.ts (91%) create mode 100644 x-pack/plugins/file_upload/server/get_time_field_range.ts delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_fields_stats.scss rename x-pack/plugins/ml/public/application/datavisualizer/file_based/{index.ts => index.tsx} (100%) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index c7fffb09248e922..64a62e365678485 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -392,6 +392,10 @@ actitivies. |The features plugin enhance Kibana with a per-feature privilege system. +|{kib-repo}blob/{branch}/x-pack/plugins/file_data_visualizer[fileDataVisualizer] +|WARNING: Missing README. + + |{kib-repo}blob/{branch}/x-pack/plugins/file_upload[fileUpload] |WARNING: Missing README. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index c7b98e6f1764349..1d1938749413687 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -106,6 +106,7 @@ pageLoadAssetSize: indexPatternFieldEditor: 90489 osquery: 107090 fileUpload: 25664 + fileDataVisualizer: 27530 banners: 17946 mapsEms: 26072 timelines: 28613 diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 3fee52ff55857fa..4a03478800fc876 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -20,6 +20,7 @@ "xpack.endpoint": "plugins/endpoint", "xpack.enterpriseSearch": "plugins/enterprise_search", "xpack.features": "plugins/features", + "xpack.fileDataVisualizer": "plugins/file_data_visualizer", "xpack.fileUpload": "plugins/file_upload", "xpack.globalSearch": ["plugins/global_search"], "xpack.globalSearchBar": ["plugins/global_search_bar"], diff --git a/x-pack/plugins/file_data_visualizer/common/constants.ts b/x-pack/plugins/file_data_visualizer/common/constants.ts new file mode 100644 index 000000000000000..819549a7eb4e6fc --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/common/constants.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const UI_SETTING_MAX_FILE_SIZE = 'fileUpload:maxFileSize'; + +export const MB = Math.pow(2, 20); +export const MAX_FILE_SIZE = '100MB'; +export const MAX_FILE_SIZE_BYTES = 104857600; // 100MB + +export const ABSOLUTE_MAX_FILE_SIZE_BYTES = 1073741274; // 1GB +export const FILE_SIZE_DISPLAY_FORMAT = '0,0.[0] b'; + +// Value to use in the Elasticsearch index mapping meta data to identify the +// index as having been created by the File Data Visualizer. +export const INDEX_META_DATA_CREATED_BY = 'file-data-visualizer'; + +export const JOB_FIELD_TYPES = { + BOOLEAN: 'boolean', + DATE: 'date', + GEO_POINT: 'geo_point', + GEO_SHAPE: 'geo_shape', + IP: 'ip', + KEYWORD: 'keyword', + NUMBER: 'number', + TEXT: 'text', + UNKNOWN: 'unknown', +} as const; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts b/x-pack/plugins/file_data_visualizer/common/index.ts similarity index 72% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts rename to x-pack/plugins/file_data_visualizer/common/index.ts index cbefc12833d2d10..f4d74984a7d78cb 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts +++ b/x-pack/plugins/file_data_visualizer/common/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { createUrlOverrides, processResults, readFile, DEFAULT_LINES_TO_SAMPLE } from './utils'; +export * from './constants'; +export * from './types'; diff --git a/x-pack/plugins/file_data_visualizer/common/types.ts b/x-pack/plugins/file_data_visualizer/common/types.ts new file mode 100644 index 000000000000000..edfe8b3575c8dcc --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/common/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { JOB_FIELD_TYPES } from './constants'; + +export type InputData = any[]; + +export type JobFieldType = typeof JOB_FIELD_TYPES[keyof typeof JOB_FIELD_TYPES]; + +export interface DataVisualizerTableState { + pageSize: number; + pageIndex: number; + sortField: string; + sortDirection: string; + visibleFieldTypes: string[]; + visibleFieldNames: string[]; + showDistributions: boolean; +} diff --git a/x-pack/plugins/file_data_visualizer/jest.config.js b/x-pack/plugins/file_data_visualizer/jest.config.js new file mode 100644 index 000000000000000..90d4cfb81f11faf --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/jest.config.js @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/file_data_visualizer'], +}; diff --git a/x-pack/plugins/file_data_visualizer/kibana.json b/x-pack/plugins/file_data_visualizer/kibana.json new file mode 100644 index 000000000000000..721352cff7c9579 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/kibana.json @@ -0,0 +1,27 @@ +{ + "id": "fileDataVisualizer", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": [ + "data", + "usageCollection", + "embeddable", + "share", + "discover", + "fileUpload" + ], + "optionalPlugins": [ + "security", + "maps" + ], + "requiredBundles": [ + "kibanaReact", + "maps", + "esUiShared" + ], + "extraPublicDirs": [ + "common" + ] +} diff --git a/x-pack/plugins/file_data_visualizer/public/api/index.ts b/x-pack/plugins/file_data_visualizer/public/api/index.ts new file mode 100644 index 000000000000000..13efd801333490d --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/api/index.ts @@ -0,0 +1,14 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { lazyLoadModules } from '../lazy_load_bundle'; +import { FileDataVisualizer } from '../application'; + +export async function getFileDataVisualizerComponent(): Promise { + const modules = await lazyLoadModules(); + return modules.FileDataVisualizer; +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/_index.scss rename to x-pack/plugins/file_data_visualizer/public/application/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/_index.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/_about_panel.scss b/x-pack/plugins/file_data_visualizer/public/application/components/about_panel/_about_panel.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/_about_panel.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/about_panel/_about_panel.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/about_panel/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/_index.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/about_panel/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/about_panel/about_panel.tsx similarity index 93% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/about_panel/about_panel.tsx index c768a422cfa5a70..e4f59c492fa1ca7 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/about_panel/about_panel.tsx @@ -43,7 +43,7 @@ export const AboutPanel: FC = ({ onFilePickerChange }) => { {

diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/about_panel/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/about_panel/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/about_panel/welcome_content.tsx similarity index 78% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/about_panel/welcome_content.tsx index 2c441e42dea2fe8..684b6dadcb29048 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/about_panel/welcome_content.tsx @@ -21,26 +21,22 @@ import { import { ExperimentalBadge } from '../experimental_badge'; -import { useMlKibana } from '../../../../contexts/kibana'; +import { useFileDataVisualizerKibana } from '../../kibana_context'; export const WelcomeContent: FC = () => { const toolTipContent = i18n.translate( - 'xpack.ml.fileDatavisualizer.welcomeContent.experimentalFeatureTooltip', + 'xpack.fileDataVisualizer.welcomeContent.experimentalFeatureTooltip', { defaultMessage: "Experimental feature. We'd love to hear your feedback.", } ); const { - services: { fileUpload }, - } = useMlKibana(); - - if (fileUpload === undefined) { - // eslint-disable-next-line no-console - console.error('File upload plugin not available'); - return null; - } - const maxFileSize = fileUpload.getMaxBytesFormatted(); + services: { + fileUpload: { getMaxBytesFormatted }, + }, + } = useFileDataVisualizerKibana(); + const maxFileSize = getMaxBytesFormatted(); return ( @@ -51,7 +47,7 @@ export const WelcomeContent: FC = () => {

, @@ -63,7 +59,7 @@ export const WelcomeContent: FC = () => {

@@ -73,7 +69,7 @@ export const WelcomeContent: FC = () => {

@@ -87,7 +83,7 @@ export const WelcomeContent: FC = () => {

@@ -103,7 +99,7 @@ export const WelcomeContent: FC = () => {

@@ -119,7 +115,7 @@ export const WelcomeContent: FC = () => {

@@ -130,7 +126,7 @@ export const WelcomeContent: FC = () => {

@@ -140,7 +136,7 @@ export const WelcomeContent: FC = () => {

= ({ results }) => { const items = createDisplayItems(results); @@ -19,7 +19,7 @@ export const AnalysisSummary: FC<{ results: FindFileStructureResponse }> = ({ re

@@ -37,7 +37,7 @@ function createDisplayItems(results: FindFileStructureResponse) { { title: ( ), @@ -53,7 +53,7 @@ function createDisplayItems(results: FindFileStructureResponse) { items.push({ title: ( ), @@ -64,7 +64,7 @@ function createDisplayItems(results: FindFileStructureResponse) { items.push({ title: ( ), @@ -74,7 +74,7 @@ function createDisplayItems(results: FindFileStructureResponse) { items.push({ title: ( ), @@ -87,7 +87,7 @@ function createDisplayItems(results: FindFileStructureResponse) { items.push({ title: ( ), @@ -99,7 +99,7 @@ function createDisplayItems(results: FindFileStructureResponse) { items.push({ title: ( ), @@ -111,7 +111,7 @@ function createDisplayItems(results: FindFileStructureResponse) { items.push({ title: ( = ({ mode, onChangeMode, onCancel, di content={ disableImport ? ( ) : null @@ -52,7 +52,7 @@ export const BottomBar: FC = ({ mode, onChangeMode, onCancel, di data-test-subj="mlFileDataVisOpenImportPageButton" > @@ -61,7 +61,7 @@ export const BottomBar: FC = ({ mode, onChangeMode, onCancel, di onCancel()}> @@ -76,7 +76,7 @@ export const BottomBar: FC = ({ mode, onChangeMode, onCancel, di onChangeMode(DATAVISUALIZER_MODE.READ)}> @@ -84,7 +84,7 @@ export const BottomBar: FC = ({ mode, onChangeMode, onCancel, di onCancel()}> diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/bottom_bar/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/bottom_bar/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/bottom_bar/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/bottom_bar/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/combined_field_label.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/combined_field_label.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/combined_field_label.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/combined_field_label.tsx diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/combined_fields_form.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/combined_fields_form.tsx similarity index 89% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/combined_fields_form.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/combined_fields_form.tsx index 5c63fd757b07ba6..fddab3edc3ec0fe 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/combined_fields_form.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/combined_fields_form.tsx @@ -29,7 +29,7 @@ import { removeCombinedFieldsFromMappings, removeCombinedFieldsFromPipeline, } from './utils'; -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; +import { FindFileStructureResponse } from '../../../../../file_upload/common'; interface Props { mappingsString: string; @@ -110,7 +110,7 @@ export class CombinedFieldsForm extends Component { return JSON.parse(this.props.mappingsString); } catch (error) { throw new Error( - i18n.translate('xpack.ml.fileDatavisualizer.combinedFieldsForm.mappingsParseError', { + i18n.translate('xpack.fileDataVisualizer.combinedFieldsForm.mappingsParseError', { defaultMessage: 'Error parsing mappings: {error}', values: { error: error.message }, }) @@ -123,7 +123,7 @@ export class CombinedFieldsForm extends Component { return JSON.parse(this.props.pipelineString); } catch (error) { throw new Error( - i18n.translate('xpack.ml.fileDatavisualizer.combinedFieldsForm.pipelineParseError', { + i18n.translate('xpack.fileDataVisualizer.combinedFieldsForm.pipelineParseError', { defaultMessage: 'Error parsing pipeline: {error}', values: { error: error.message }, }) @@ -149,7 +149,7 @@ export class CombinedFieldsForm extends Component { }; render() { - const geoPointLabel = i18n.translate('xpack.ml.fileDatavisualizer.geoPointCombinedFieldLabel', { + const geoPointLabel = i18n.translate('xpack.fileDataVisualizer.geoPointCombinedFieldLabel', { defaultMessage: 'Add geo point field', }); const panels = [ @@ -176,7 +176,7 @@ export class CombinedFieldsForm extends Component { ]; return ( @@ -192,11 +192,11 @@ export class CombinedFieldsForm extends Component { iconType="trash" color="danger" onClick={this.removeCombinedField.bind(null, idx)} - title={i18n.translate('xpack.ml.fileDatavisualizer.removeCombinedFieldsLabel', { + title={i18n.translate('xpack.fileDataVisualizer.removeCombinedFieldsLabel', { defaultMessage: 'Remove combined field', })} aria-label={i18n.translate( - 'xpack.ml.fileDatavisualizer.removeCombinedFieldsLabel', + 'xpack.fileDataVisualizer.removeCombinedFieldsLabel', { defaultMessage: 'Remove combined field', } @@ -216,7 +216,7 @@ export class CombinedFieldsForm extends Component { isDisabled={this.props.isDisabled} > diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/combined_fields_read_only_form.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/combined_fields_read_only_form.tsx similarity index 83% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/combined_fields_read_only_form.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/combined_fields_read_only_form.tsx index dc8e839b7defe22..978383f8e5e10a8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/combined_fields_read_only_form.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/combined_fields_read_only_form.tsx @@ -20,10 +20,10 @@ export function CombinedFieldsReadOnlyForm({ }) { return combinedFields.length ? ( diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/geo_point.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/geo_point.tsx similarity index 90% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/geo_point.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/geo_point.tsx index 5ae2e5de681c3e7..578d22384be3331 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/geo_point.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/geo_point.tsx @@ -29,7 +29,7 @@ import { getFieldNames, getNameCollisionMsg, } from './utils'; -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; +import { FindFileStructureResponse } from '../../../../../file_upload/common'; interface Props { addCombinedField: (combinedField: CombinedField) => void; @@ -119,7 +119,7 @@ export class GeoPointForm extends Component { return ( @@ -131,7 +131,7 @@ export class GeoPointForm extends Component { @@ -143,7 +143,7 @@ export class GeoPointForm extends Component { { onChange={this.onGeoPointFieldChange} isInvalid={this.state.geoPointFieldError !== ''} aria-label={i18n.translate( - 'xpack.ml.fileDatavisualizer.geoPointForm.geoPointFieldAriaLabel', + 'xpack.fileDataVisualizer.geoPointForm.geoPointFieldAriaLabel', { defaultMessage: 'Geo point field, required field', } @@ -179,7 +179,7 @@ export class GeoPointForm extends Component { onClick={this.onSubmit} > diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/types.ts b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/types.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/types.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/types.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.test.ts b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/utils.test.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.test.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/utils.test.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.ts b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/utils.ts similarity index 97% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/utils.ts index ab08398fcda025b..efd166d4821c5a4 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.ts +++ b/x-pack/plugins/file_data_visualizer/public/application/components/combined_fields/utils.ts @@ -13,7 +13,7 @@ import { FindFileStructureResponse, IngestPipeline, Mappings, -} from '../../../../../../../file_upload/common'; +} from '../../../../../file_upload/common'; const COMMON_LAT_NAMES = ['latitude', 'lat']; const COMMON_LON_NAMES = ['longitude', 'long', 'lon']; @@ -127,7 +127,7 @@ export function createGeoPointCombinedField( } export function getNameCollisionMsg(name: string) { - return i18n.translate('xpack.ml.fileDatavisualizer.nameCollisionMsg', { + return i18n.translate('xpack.fileDataVisualizer.nameCollisionMsg', { defaultMessage: '"{name}" already exists, please provide a unique name', values: { name }, }); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/__snapshots__/overrides.test.js.snap similarity index 96% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/__snapshots__/overrides.test.js.snap index 6ab89fe3e4b2d87..00dd652457daf3a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap +++ b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/__snapshots__/overrides.test.js.snap @@ -13,7 +13,7 @@ exports[`Overrides render overrides 1`] = ` label={ } @@ -33,7 +33,7 @@ exports[`Overrides render overrides 1`] = ` label={ } @@ -94,7 +94,7 @@ exports[`Overrides render overrides 1`] = ` label={ } @@ -335,7 +335,7 @@ exports[`Overrides render overrides 1`] = ` label={ } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/_edit_flyout.scss b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/_edit_flyout.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/_edit_flyout.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/_edit_flyout.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/_index.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/edit_flyout.js b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/edit_flyout.js similarity index 91% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/edit_flyout.js rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/edit_flyout.js index c26e504087b46f5..7cdee6f823bd64c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/edit_flyout.js +++ b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/edit_flyout.js @@ -69,7 +69,7 @@ export class EditFlyout extends Component {

@@ -96,7 +96,7 @@ export class EditFlyout extends Component { @@ -108,7 +108,7 @@ export class EditFlyout extends Component { fill > diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/index.js b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/index.js similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/index.js rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/index.js diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/index.js b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/options/index.js similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/index.js rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/options/index.js diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/option_lists.js b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/options/option_lists.js similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/option_lists.js rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/options/option_lists.js diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/options.js b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/options/options.js similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/options/options.js rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/options/options.js diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.js b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides.js similarity index 90% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.js rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides.js index 23c7b869f5e6f5a..cb0839b335a9717 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.js +++ b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides.js @@ -31,7 +31,7 @@ import { // getCharsetOptions, } from './options'; import { isTimestampFormatValid } from './overrides_validation'; -import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { TIMESTAMP_OPTIONS, CUSTOM_DROPDOWN_OPTION } from './options/option_lists'; @@ -52,7 +52,7 @@ class OverridesUI extends Component { } linesToSampleErrors = i18n.translate( - 'xpack.ml.fileDatavisualizer.editFlyout.overrides.linesToSampleErrorMessage', + 'xpack.fileDataVisualizer.editFlyout.overrides.linesToSampleErrorMessage', { defaultMessage: 'Value must be greater than {min} and less than or equal to {max}', values: { @@ -63,7 +63,7 @@ class OverridesUI extends Component { ); customTimestampFormatErrors = i18n.translate( - 'xpack.ml.fileDatavisualizer.editFlyout.overrides.customTimestampFormatErrorMessage', + 'xpack.fileDataVisualizer.editFlyout.overrides.customTimestampFormatErrorMessage', { defaultMessage: `Timestamp format must be a combination of these Java date/time formats: yy, yyyy, M, MM, MMM, MMMM, d, dd, EEE, EEEE, H, HH, h, mm, ss, S through SSSSSSSSS, a, XX, XXX, zzz`, @@ -274,12 +274,9 @@ class OverridesUI extends Component { const timestampFormatHelp = ( - {i18n.translate( - 'xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampFormatHelpText', - { - defaultMessage: 'See more on accepted formats', - } - )} + {i18n.translate('xpack.fileDataVisualizer.editFlyout.overrides.timestampFormatHelpText', { + defaultMessage: 'See more on accepted formats', + })} ); @@ -291,7 +288,7 @@ class OverridesUI extends Component { isInvalid={linesToSampleValid === false} label={ } @@ -306,7 +303,7 @@ class OverridesUI extends Component { } @@ -324,7 +321,7 @@ class OverridesUI extends Component { } @@ -341,7 +338,7 @@ class OverridesUI extends Component { } @@ -353,7 +350,7 @@ class OverridesUI extends Component { } @@ -372,7 +369,7 @@ class OverridesUI extends Component { id={'hasHeaderRow'} label={ } @@ -386,7 +383,7 @@ class OverridesUI extends Component { id={'shouldTrimFields'} label={ } @@ -401,7 +398,7 @@ class OverridesUI extends Component { } @@ -418,7 +415,7 @@ class OverridesUI extends Component { helpText={timestampFormatHelp} label={ } @@ -437,7 +434,7 @@ class OverridesUI extends Component { isInvalid={timestampFormatValid === false} label={ } @@ -453,7 +450,7 @@ class OverridesUI extends Component { } @@ -483,7 +480,7 @@ class OverridesUI extends Component {

diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.test.js b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides.test.js similarity index 94% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.test.js rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides.test.js index 764ae6fb2b5362f..8e11d5150359d92 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides.test.js +++ b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides.test.js @@ -10,7 +10,7 @@ import React from 'react'; import { Overrides } from './overrides'; -jest.mock('../../../../../../../../../src/plugins/kibana_react/public', () => ({ +jest.mock('../../../../../../../src/plugins/kibana_react/public', () => ({ withKibana: (comp) => { return comp; }, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides_validation.js b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides_validation.js similarity index 84% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides_validation.js rename to x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides_validation.js index 79a44bd8b5ac673..c833d55351b6dcc 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/overrides_validation.js +++ b/x-pack/plugins/file_data_visualizer/public/application/components/edit_flyout/overrides_validation.js @@ -41,7 +41,7 @@ export function isTimestampFormatValid(timestampFormat) { if (timestampFormat.indexOf('?') >= 0) { result.isValid = false; result.errorMessage = i18n.translate( - 'xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampQuestionMarkValidationErrorMessage', + 'xpack.fileDataVisualizer.editFlyout.overrides.timestampQuestionMarkValidationErrorMessage', { defaultMessage: 'Timestamp format {timestampFormat} not supported because it contains a question mark character ({fieldPlaceholder})', @@ -86,7 +86,7 @@ export function isTimestampFormatValid(timestampFormat) { result.isValid = false; result.errorMessage = i18n.translate( - 'xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampLetterValidationErrorMessage', + 'xpack.fileDataVisualizer.editFlyout.overrides.timestampLetterValidationErrorMessage', { defaultMessage: 'Letter { length, plural, one { {lg} } other { group {lg} } } in {format} is not supported', @@ -101,9 +101,10 @@ export function isTimestampFormatValid(timestampFormat) { if (curChar === 'S') { // disable exceeds maximum line length error so i18n check passes result.errorMessage = i18n.translate( - 'xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampLetterSValidationErrorMessage', + 'xpack.fileDataVisualizer.editFlyout.overrides.timestampLetterSValidationErrorMessage', { - defaultMessage: 'Letter { length, plural, one { {lg} } other { group {lg} } } in {format} is not supported because it is not preceded by ss and a separator from {sep}', // eslint-disable-line + defaultMessage: + 'Letter { length, plural, one { {lg} } other { group {lg} } } in {format} is not supported because it is not preceded by ss and a separator from {sep}', // eslint-disable-line values: { length, lg: letterGroup, @@ -127,7 +128,7 @@ export function isTimestampFormatValid(timestampFormat) { if (prevLetterGroup == null) { result.isValid = false; result.errorMessage = i18n.translate( - 'xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampEmptyValidationErrorMessage', + 'xpack.fileDataVisualizer.editFlyout.overrides.timestampEmptyValidationErrorMessage', { defaultMessage: 'No time format letter groups in timestamp format {timestampFormat}', values: { diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/_embedded_map.scss b/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/_embedded_map.scss new file mode 100644 index 000000000000000..99ee60f62bb21e0 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/_embedded_map.scss @@ -0,0 +1,8 @@ +.embeddedMapContent { + width: 100%; + height: 100%; + display: flex; + flex: 1 1 100%; + z-index: 1; + min-height: 0; // Absolute must for Firefox to scroll contents +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/_index.scss new file mode 100644 index 000000000000000..5b3c6b4990ff114 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/_index.scss @@ -0,0 +1 @@ +@import 'embedded_map'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/embedded_map.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/embedded_map.tsx new file mode 100644 index 000000000000000..42bc5ebf6122718 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/embedded_map.tsx @@ -0,0 +1,155 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useRef, useState } from 'react'; + +import { htmlIdGenerator } from '@elastic/eui'; +import { LayerDescriptor } from '../../../../../maps/common/descriptor_types'; +import { INITIAL_LOCATION } from '../../../../../maps/common/constants'; +import { + MapEmbeddable, + MapEmbeddableInput, + MapEmbeddableOutput, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../maps/public/embeddable'; +import { MAP_SAVED_OBJECT_TYPE, RenderTooltipContentParams } from '../../../../../maps/public'; +import { + EmbeddableFactory, + ErrorEmbeddable, + isErrorEmbeddable, + ViewMode, +} from '../../../../../../../src/plugins/embeddable/public'; +import { useFileDataVisualizerKibana } from '../../kibana_context'; + +export function EmbeddedMapComponent({ + layerList, + mapEmbeddableInput, + renderTooltipContent, +}: { + layerList: LayerDescriptor[]; + mapEmbeddableInput?: MapEmbeddableInput; + renderTooltipContent?: (params: RenderTooltipContentParams) => JSX.Element; +}) { + const [embeddable, setEmbeddable] = useState(); + + const embeddableRoot: React.RefObject = useRef(null); + const baseLayers = useRef(); + + const { + services: { embeddable: embeddablePlugin, maps: mapsPlugin }, + } = useFileDataVisualizerKibana(); + + const factory: + | EmbeddableFactory + | undefined = embeddablePlugin + ? embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE) + : undefined; + + // Update the layer list with updated geo points upon refresh + useEffect(() => { + async function updateIndexPatternSearchLayer() { + if ( + embeddable && + !isErrorEmbeddable(embeddable) && + Array.isArray(layerList) && + Array.isArray(baseLayers.current) + ) { + embeddable.setLayerList([...baseLayers.current, ...layerList]); + } + } + updateIndexPatternSearchLayer(); + }, [embeddable, layerList]); + + useEffect(() => { + async function setupEmbeddable() { + if (!factory) { + // eslint-disable-next-line no-console + console.error('Map embeddable not found.'); + return; + } + const input: MapEmbeddableInput = { + id: htmlIdGenerator()(), + attributes: { title: '' }, + filters: [], + hidePanelTitles: true, + refreshConfig: { + value: 0, + pause: false, + }, + viewMode: ViewMode.VIEW, + isLayerTOCOpen: false, + hideFilterActions: true, + // can use mapSettings to center map on anomalies + mapSettings: { + disableInteractive: false, + hideToolbarOverlay: false, + hideLayerControl: false, + hideViewControl: false, + initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent + autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query + }, + }; + + const embeddableObject = await factory.create(input); + + if (embeddableObject && !isErrorEmbeddable(embeddableObject)) { + const basemapLayerDescriptor = mapsPlugin + ? await mapsPlugin.createLayerDescriptors.createBasemapLayerDescriptor() + : null; + + if (basemapLayerDescriptor) { + baseLayers.current = [basemapLayerDescriptor]; + await embeddableObject.setLayerList(baseLayers.current); + } + } + + setEmbeddable(embeddableObject); + } + + setupEmbeddable(); + // we want this effect to execute exactly once after the component mounts + // eslint-disable-next-line + }, []); + + useEffect(() => { + if (embeddable && !isErrorEmbeddable(embeddable) && mapEmbeddableInput !== undefined) { + embeddable.updateInput(mapEmbeddableInput); + } + }, [embeddable, mapEmbeddableInput]); + + useEffect(() => { + if (embeddable && !isErrorEmbeddable(embeddable) && renderTooltipContent !== undefined) { + embeddable.setRenderTooltipContent(renderTooltipContent); + } + }, [embeddable, renderTooltipContent]); + + // We can only render after embeddable has already initialized + useEffect(() => { + if (embeddableRoot.current && embeddable) { + embeddable.render(embeddableRoot.current); + } + }, [embeddable, embeddableRoot]); + + if (!embeddablePlugin) { + // eslint-disable-next-line no-console + console.error('Embeddable start plugin not found'); + return null; + } + if (!mapsPlugin) { + // eslint-disable-next-line no-console + console.error('Maps start plugin not found'); + return null; + } + + return ( +
+ ); +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/index.ts new file mode 100644 index 000000000000000..ee11a18345f64b9 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/embedded_map/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { EmbeddedMapComponent } from './embedded_map'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/examples_list/examples_list.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/examples_list/examples_list.tsx new file mode 100644 index 000000000000000..1c533075af27b36 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/examples_list/examples_list.tsx @@ -0,0 +1,59 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +import { EuiListGroup, EuiListGroupItem } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { ExpandedRowFieldHeader } from '../stats_table/components/expanded_row_field_header'; +interface Props { + examples: Array; +} + +export const ExamplesList: FC = ({ examples }) => { + if (examples === undefined || examples === null || !Array.isArray(examples)) { + return null; + } + let examplesContent; + if (examples.length === 0) { + examplesContent = ( + + ); + } else { + examplesContent = examples.map((example, i) => { + return ( + + ); + }); + } + + return ( +
+ + + + + {examplesContent} + +
+ ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/examples_list/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/examples_list/index.ts new file mode 100644 index 000000000000000..966c844987002b3 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/examples_list/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ExamplesList } from './examples_list'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/file_based_expanded_row.tsx similarity index 69% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/file_based_expanded_row.tsx index 01b5da5c42ccc5f..620bcfef8ff6c48 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/file_based_expanded_row.tsx @@ -14,10 +14,10 @@ import { OtherContent, TextContent, NumberContent, -} from '../../../stats_table/components/field_data_expanded_row'; +} from '../stats_table/components/field_data_expanded_row'; import { GeoPointContent } from './geo_point_content/geo_point_content'; -import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; -import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config'; +import { JOB_FIELD_TYPES } from '../../../../common'; +import type { FileBasedFieldVisConfig } from '../stats_table/types/field_vis_config'; export const FileBasedDataVisualizerExpandedRow = ({ item }: { item: FileBasedFieldVisConfig }) => { const config = item; @@ -25,25 +25,25 @@ export const FileBasedDataVisualizerExpandedRow = ({ item }: { item: FileBasedFi function getCardContent() { switch (type) { - case ML_JOB_FIELD_TYPES.NUMBER: + case JOB_FIELD_TYPES.NUMBER: return ; - case ML_JOB_FIELD_TYPES.BOOLEAN: + case JOB_FIELD_TYPES.BOOLEAN: return ; - case ML_JOB_FIELD_TYPES.DATE: + case JOB_FIELD_TYPES.DATE: return ; - case ML_JOB_FIELD_TYPES.GEO_POINT: + case JOB_FIELD_TYPES.GEO_POINT: return ; - case ML_JOB_FIELD_TYPES.IP: + case JOB_FIELD_TYPES.IP: return ; - case ML_JOB_FIELD_TYPES.KEYWORD: + case JOB_FIELD_TYPES.KEYWORD: return ; - case ML_JOB_FIELD_TYPES.TEXT: + case JOB_FIELD_TYPES.TEXT: return ; default: @@ -53,7 +53,7 @@ export const FileBasedDataVisualizerExpandedRow = ({ item }: { item: FileBasedFi return (
{getCardContent()} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts b/x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/geo_point_content/format_utils.ts similarity index 96% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/geo_point_content/format_utils.ts index 30e07a6040dabde..69e361aba9bca8d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts +++ b/x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/geo_point_content/format_utils.ts @@ -8,7 +8,7 @@ import { Feature, Point } from 'geojson'; import { euiPaletteColorBlind } from '@elastic/eui'; import { DEFAULT_GEO_REGEX } from './geo_point_content'; -import { SOURCE_TYPES } from '../../../../../../../../maps/common/constants'; +import { SOURCE_TYPES } from '../../../../../../maps/common/constants'; export const convertWKTGeoToLonLat = ( value: string | number diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/geo_point_content/geo_point_content.tsx similarity index 78% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/geo_point_content/geo_point_content.tsx index b420ab43f56f413..c395b06059e8f7b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/geo_point_content/geo_point_content.tsx @@ -9,12 +9,12 @@ import React, { FC, useMemo } from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { Feature, Point } from 'geojson'; -import type { FieldDataRowProps } from '../../../../stats_table/types/field_data_row'; -import { DocumentStatsTable } from '../../../../stats_table/components/field_data_expanded_row/document_stats'; -import { MlEmbeddedMapComponent } from '../../../../../components/ml_embedded_map'; +import type { FieldDataRowProps } from '../../stats_table/types/field_data_row'; +import { DocumentStatsTable } from '../../stats_table/components/field_data_expanded_row/document_stats'; +import { EmbeddedMapComponent } from '../../embedded_map'; import { convertWKTGeoToLonLat, getGeoPointsLayer } from './format_utils'; -import { ExpandedRowContent } from '../../../../stats_table/components/field_data_expanded_row/expanded_row_content'; -import { ExamplesList } from '../../../../index_based/components/field_data_row/examples_list'; +import { ExpandedRowContent } from '../../stats_table/components/field_data_expanded_row/expanded_row_content'; +import { ExamplesList } from '../../examples_list'; export const DEFAULT_GEO_REGEX = RegExp('(?.+) (?.+)'); @@ -38,7 +38,7 @@ export const GeoPointContent: FC = ({ config }) => { geoPointsFeatures.push({ type: 'Feature', - id: `ml-${config.fieldName}-${i}`, + id: `fileDataVisualizer-${config.fieldName}-${i}`, geometry: { type: 'Point', coordinates: [coordinates.lat, coordinates.lon], @@ -69,10 +69,10 @@ export const GeoPointContent: FC = ({ config }) => { )} {formattedResults && Array.isArray(formattedResults.layerList) && ( - + )} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/geo_point_content/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/geo_point_content/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/expanded_row/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/_experimental_badge.scss b/x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/_experimental_badge.scss similarity index 74% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/_experimental_badge.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/_experimental_badge.scss index 016d5cd579e3fcf..8b21620542ff75c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/_experimental_badge.scss +++ b/x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/_experimental_badge.scss @@ -1,4 +1,4 @@ -.ml-experimental-badge.euiBetaBadge { +.experimental-badge.euiBetaBadge { font-size: 10px; vertical-align: middle; margin-bottom: 5px; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/_index.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/experimental_badge.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/experimental_badge.tsx similarity index 85% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/experimental_badge.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/experimental_badge.tsx index 5eef240429a481d..a067cb198914ee9 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/experimental_badge.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/experimental_badge.tsx @@ -14,10 +14,10 @@ export const ExperimentalBadge: FC<{ tooltipContent: string }> = ({ tooltipConte return ( } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/experimental_badge/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/explanation_flyout/explanation_flyout.tsx similarity index 87% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/explanation_flyout/explanation_flyout.tsx index 579f2e3340954dc..606bab514ac9f1a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/explanation_flyout/explanation_flyout.tsx @@ -20,7 +20,7 @@ import { EuiText, EuiSubSteps, } from '@elastic/eui'; -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; +import { FindFileStructureResponse } from '../../../../../file_upload/common'; interface Props { results: FindFileStructureResponse; @@ -34,7 +34,7 @@ export const ExplanationFlyout: FC = ({ results, closeFlyout }) => {

@@ -48,7 +48,7 @@ export const ExplanationFlyout: FC = ({ results, closeFlyout }) => { @@ -63,7 +63,7 @@ const Content: FC<{ explanation: string[] }> = ({ explanation }) => ( <> diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/explanation_flyout/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/explanation_flyout/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/field_data_row/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/field_data_row/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/number_content_preview.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/field_data_row/number_content_preview.tsx similarity index 84% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/number_content_preview.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/field_data_row/number_content_preview.tsx index dc164b5bf3453ab..c02976cdb38534d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/number_content_preview.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/field_data_row/number_content_preview.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { FileBasedFieldVisConfig } from '../../../stats_table/types'; +import { FileBasedFieldVisConfig } from '../stats_table/types'; export const FileBasedNumberContentPreview = ({ config }: { config: FileBasedFieldVisConfig }) => { const stats = config.stats; @@ -25,7 +25,7 @@ export const FileBasedNumberContentPreview = ({ config }: { config: FileBasedFie @@ -33,7 +33,7 @@ export const FileBasedNumberContentPreview = ({ config }: { config: FileBasedFie @@ -41,7 +41,7 @@ export const FileBasedNumberContentPreview = ({ config }: { config: FileBasedFie diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/field_names_filter.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/field_names_filter/field_names_filter.tsx similarity index 84% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/field_names_filter.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/field_names_filter/field_names_filter.tsx index 9bd16ff5dbefa3e..466722adc717932 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/field_names_filter.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/field_names_filter/field_names_filter.tsx @@ -7,11 +7,11 @@ import React, { FC, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { MultiSelectPicker } from '../../../../components/multi_select_picker'; +import { MultiSelectPicker } from '../multi_select_picker'; import type { FileBasedFieldVisConfig, FileBasedUnknownFieldVisConfig, -} from '../../../stats_table/types/field_vis_config'; +} from '../stats_table/types/field_vis_config'; interface Props { fields: Array; @@ -26,7 +26,7 @@ export const DataVisualizerFieldNamesFilter: FC = ({ }) => { const fieldNameTitle = useMemo( () => - i18n.translate('xpack.ml.dataVisualizer.fileBased.fieldNameSelect', { + i18n.translate('xpack.fileDataVisualizer.fieldNameSelect', { defaultMessage: 'Field name', }), [] diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/field_names_filter/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/field_names_filter/index.ts diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap b/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap new file mode 100644 index 000000000000000..769ebdeba9955ca --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/__snapshots__/field_type_icon.test.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FieldTypeIcon render component when type matches a field type 1`] = ` + + + +`; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/field_type_icon.test.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/field_type_icon.test.tsx new file mode 100644 index 000000000000000..d1321ad8f9f4d97 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/field_type_icon.test.tsx @@ -0,0 +1,52 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount, shallow } from 'enzyme'; + +import { FieldTypeIcon } from './field_type_icon'; +import { JOB_FIELD_TYPES } from '../../../../common'; + +describe('FieldTypeIcon', () => { + test(`render component when type matches a field type`, () => { + const typeIconComponent = shallow( + + ); + expect(typeIconComponent).toMatchSnapshot(); + }); + + test(`render with tooltip and test hovering`, () => { + // Use fake timers so we don't have to wait for the EuiToolTip timeout + jest.useFakeTimers(); + + const typeIconComponent = mount( + + ); + const container = typeIconComponent.find({ 'data-test-subj': 'fieldTypeIcon' }); + + expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(1); + + container.simulate('mouseover'); + + // Run the timers so the EuiTooltip will be visible + jest.runAllTimers(); + + typeIconComponent.update(); + expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(2); + + container.simulate('mouseout'); + + // Run the timers so the EuiTooltip will be hidden again + jest.runAllTimers(); + + typeIconComponent.update(); + expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(1); + + // Clearing all mocks will also reset fake timers. + jest.clearAllMocks(); + }); +}); diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/field_type_icon.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/field_type_icon.tsx new file mode 100644 index 000000000000000..2dd7ff635bacd17 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/field_type_icon.tsx @@ -0,0 +1,130 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +import { EuiToken, EuiToolTip } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { getJobTypeAriaLabel } from '../../util/field_types_utils'; +import { JOB_FIELD_TYPES } from '../../../../common'; +import type { JobFieldType } from '../../../../common'; + +interface FieldTypeIconProps { + tooltipEnabled: boolean; + type: JobFieldType; + fieldName?: string; + needsAria: boolean; +} + +interface FieldTypeIconContainerProps { + ariaLabel: string | null; + iconType: string; + color: string; + needsAria: boolean; + [key: string]: any; +} + +export const FieldTypeIcon: FC = ({ + tooltipEnabled = false, + type, + fieldName, + needsAria = true, +}) => { + const ariaLabel = getJobTypeAriaLabel(type); + + let iconType = 'questionInCircle'; + let color = 'euiColorVis6'; + + switch (type) { + // Set icon types and colors + case JOB_FIELD_TYPES.BOOLEAN: + iconType = 'tokenBoolean'; + color = 'euiColorVis5'; + break; + case JOB_FIELD_TYPES.DATE: + iconType = 'tokenDate'; + color = 'euiColorVis7'; + break; + case JOB_FIELD_TYPES.GEO_POINT: + case JOB_FIELD_TYPES.GEO_SHAPE: + iconType = 'tokenGeo'; + color = 'euiColorVis8'; + break; + case JOB_FIELD_TYPES.TEXT: + iconType = 'document'; + color = 'euiColorVis9'; + break; + case JOB_FIELD_TYPES.IP: + iconType = 'tokenIP'; + color = 'euiColorVis3'; + break; + case JOB_FIELD_TYPES.KEYWORD: + iconType = 'tokenText'; + color = 'euiColorVis0'; + break; + case JOB_FIELD_TYPES.NUMBER: + iconType = 'tokenNumber'; + color = fieldName !== undefined ? 'euiColorVis1' : 'euiColorVis2'; + break; + case JOB_FIELD_TYPES.UNKNOWN: + // Use defaults + break; + } + + const containerProps = { + ariaLabel, + iconType, + color, + needsAria, + }; + + if (tooltipEnabled === true) { + // wrap the inner component inside because EuiToolTip doesn't seem + // to support having another component directly inside the tooltip anchor + // see https://github.com/elastic/eui/issues/839 + return ( + + + + ); + } + + return ; +}; + +// If the tooltip is used, it will apply its events to its first inner child. +// To pass on its properties we apply `rest` to the outer `span` element. +const FieldTypeIconContainer: FC = ({ + ariaLabel, + iconType, + color, + needsAria, + ...rest +}) => { + const wrapperProps: { className: string; 'aria-label'?: string } = { + className: 'field-type-icon', + }; + if (needsAria && ariaLabel) { + wrapperProps['aria-label'] = ariaLabel; + } + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/index.ts new file mode 100644 index 000000000000000..fa825e447be304b --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/field_type_icon/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { FieldTypeIcon } from './field_type_icon'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/field_types_filter.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/field_types_filter/field_types_filter.tsx similarity index 65% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/field_types_filter.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/field_types_filter/field_types_filter.tsx index 6ad6cfc84061d50..8c5602bc625f8b7 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/field_types_filter.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/field_types_filter/field_types_filter.tsx @@ -8,13 +8,25 @@ import React, { FC, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { MultiSelectPicker, Option } from '../../../../components/multi_select_picker'; +import { MultiSelectPicker, Option } from '../multi_select_picker'; import type { FileBasedFieldVisConfig, FileBasedUnknownFieldVisConfig, -} from '../../../stats_table/types/field_vis_config'; -import { FieldTypeIcon } from '../../../../components/field_type_icon'; -import { ML_JOB_FIELD_TYPES_OPTIONS } from '../../../index_based/components/search_panel/field_type_filter'; +} from '../stats_table/types/field_vis_config'; +import { FieldTypeIcon } from '../field_type_icon'; +import { JOB_FIELD_TYPES } from '../../../../common'; + +const JOB_FIELD_TYPES_OPTIONS = { + [JOB_FIELD_TYPES.BOOLEAN]: { name: 'Boolean', icon: 'tokenBoolean' }, + [JOB_FIELD_TYPES.DATE]: { name: 'Date', icon: 'tokenDate' }, + [JOB_FIELD_TYPES.GEO_POINT]: { name: 'Geo point', icon: 'tokenGeo' }, + [JOB_FIELD_TYPES.GEO_SHAPE]: { name: 'Geo shape', icon: 'tokenGeo' }, + [JOB_FIELD_TYPES.IP]: { name: 'IP address', icon: 'tokenIP' }, + [JOB_FIELD_TYPES.KEYWORD]: { name: 'Keyword', icon: 'tokenKeyword' }, + [JOB_FIELD_TYPES.NUMBER]: { name: 'Number', icon: 'tokenNumber' }, + [JOB_FIELD_TYPES.TEXT]: { name: 'Text', icon: 'tokenString' }, + [JOB_FIELD_TYPES.UNKNOWN]: { name: 'Unknown' }, +}; interface Props { fields: Array; @@ -29,7 +41,7 @@ export const DataVisualizerFieldTypesFilter: FC = ({ }) => { const fieldNameTitle = useMemo( () => - i18n.translate('xpack.ml.dataVisualizer.fileBased.fieldTypeSelect', { + i18n.translate('xpack.fileDataVisualizer.fieldTypeSelect', { defaultMessage: 'Field type', }), [] @@ -42,9 +54,9 @@ export const DataVisualizerFieldTypesFilter: FC = ({ if ( type !== undefined && !fieldTypesTracker.has(type) && - ML_JOB_FIELD_TYPES_OPTIONS[type] !== undefined + JOB_FIELD_TYPES_OPTIONS[type] !== undefined ) { - const item = ML_JOB_FIELD_TYPES_OPTIONS[type]; + const item = JOB_FIELD_TYPES_OPTIONS[type]; fieldTypesTracker.add(type); fieldTypes.push({ diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/field_types_filter/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/field_types_filter/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/create_fields.ts b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/create_fields.ts similarity index 80% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/create_fields.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/create_fields.ts index fdbb35d27c531e2..f45071d6e96b5a7 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/create_fields.ts +++ b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/create_fields.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; +import { FindFileStructureResponse } from '../../../../../file_upload/common'; import { getFieldNames, getSupportedFieldType } from './get_field_names'; -import { FileBasedFieldVisConfig } from '../../../stats_table/types'; -import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; -import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; +import { FileBasedFieldVisConfig } from '../stats_table/types'; +import { JOB_FIELD_TYPES } from '../../../../common'; +import { roundToDecimalPlace } from '../utils'; export function createFields(results: FindFileStructureResponse) { const { @@ -28,20 +28,20 @@ export function createFields(results: FindFileStructureResponse) { if (fieldStats[name] !== undefined) { const field: FileBasedFieldVisConfig = { fieldName: name, - type: ML_JOB_FIELD_TYPES.UNKNOWN, + type: JOB_FIELD_TYPES.UNKNOWN, }; const f = fieldStats[name]; const m = mappings.properties[name]; // sometimes the timestamp field is not in the mappings, and so our // collection of fields will be missing a time field with a type of date - if (name === timestampField && field.type === ML_JOB_FIELD_TYPES.UNKNOWN) { - field.type = ML_JOB_FIELD_TYPES.DATE; + if (name === timestampField && field.type === JOB_FIELD_TYPES.UNKNOWN) { + field.type = JOB_FIELD_TYPES.DATE; } if (m !== undefined) { field.type = getSupportedFieldType(m.type); - if (field.type === ML_JOB_FIELD_TYPES.NUMBER) { + if (field.type === JOB_FIELD_TYPES.NUMBER) { numericFieldsCount += 1; } if (m.format !== undefined) { @@ -71,7 +71,7 @@ export function createFields(results: FindFileStructureResponse) { } if (f.top_hits !== undefined) { - if (field.type === ML_JOB_FIELD_TYPES.TEXT) { + if (field.type === JOB_FIELD_TYPES.TEXT) { _stats = { ..._stats, examples: f.top_hits.map((hit) => hit.value), @@ -84,7 +84,7 @@ export function createFields(results: FindFileStructureResponse) { } } - if (field.type === ML_JOB_FIELD_TYPES.DATE) { + if (field.type === JOB_FIELD_TYPES.DATE) { _stats = { ..._stats, earliest: f.earliest, @@ -99,9 +99,9 @@ export function createFields(results: FindFileStructureResponse) { // this could be the message field for a semi-structured log file or a // field which the endpoint has not been able to work out any information for const type = - mappings.properties[name] && mappings.properties[name].type === ML_JOB_FIELD_TYPES.TEXT - ? ML_JOB_FIELD_TYPES.TEXT - : ML_JOB_FIELD_TYPES.UNKNOWN; + mappings.properties[name] && mappings.properties[name].type === JOB_FIELD_TYPES.TEXT + ? JOB_FIELD_TYPES.TEXT + : JOB_FIELD_TYPES.UNKNOWN; return { fieldName: name, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/fields_stats_grid.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/fields_stats_grid.tsx similarity index 79% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/fields_stats_grid.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/fields_stats_grid.tsx index 1029d58b4c6395b..3b5b1bbf81dba5b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/fields_stats_grid.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/fields_stats_grid.tsx @@ -5,29 +5,25 @@ * 2.0. */ -import React, { useMemo, FC } from 'react'; +import React, { useMemo, FC, useState } from 'react'; import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; -import type { FindFileStructureResponse } from '../../../../../../../file_upload/common'; -import { DataVisualizerTable, ItemIdToExpandedRowMap } from '../../../stats_table'; -import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config'; +import type { FindFileStructureResponse } from '../../../../../file_upload/common'; +import type { DataVisualizerTableState } from '../../../../common'; +import { DataVisualizerTable, ItemIdToExpandedRowMap } from '../stats_table'; +import type { FileBasedFieldVisConfig } from '../stats_table/types/field_vis_config'; import { FileBasedDataVisualizerExpandedRow } from '../expanded_row'; import { DataVisualizerFieldNamesFilter } from '../field_names_filter'; import { DataVisualizerFieldTypesFilter } from '../field_types_filter'; import { createFields } from './create_fields'; import { filterFields } from './filter_fields'; -import { usePageUrlState } from '../../../../util/url_state'; -import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator'; -import { - MetricFieldsCount, - TotalFieldsCount, -} from '../../../stats_table/components/field_count_stats'; -import type { DataVisualizerFileBasedAppState } from '../../../../../../common/types/ml_url_generator'; +import { MetricFieldsCount, TotalFieldsCount } from '../stats_table/components/field_count_stats'; interface Props { results: FindFileStructureResponse; } -export const getDefaultDataVisualizerListState = (): Required => ({ + +export const getDefaultDataVisualizerListState = (): DataVisualizerTableState => ({ pageIndex: 0, pageSize: 10, sortField: 'fieldName', @@ -52,13 +48,11 @@ function getItemIdToExpandedRowMap( export const FieldsStatsGrid: FC = ({ results }) => { const restorableDefaults = getDefaultDataVisualizerListState(); - const [ - dataVisualizerListState, - setDataVisualizerListState, - ] = usePageUrlState( - ML_PAGES.DATA_VISUALIZER_FILE, + + const [dataVisualizerListState, setDataVisualizerListState] = useState( restorableDefaults ); + const visibleFieldTypes = dataVisualizerListState.visibleFieldTypes ?? restorableDefaults.visibleFieldTypes; const setVisibleFieldTypes = (values: string[]) => { @@ -73,11 +67,11 @@ export const FieldsStatsGrid: FC = ({ results }) => { const { fields, totalFieldsCount, totalMetricFieldsCount } = useMemo( () => createFields(results), - [results, visibleFieldNames, visibleFieldTypes] + [results] ); const { filteredFields, visibleFieldsCount, visibleMetricsCount } = useMemo( () => filterFields(fields, visibleFieldNames, visibleFieldTypes), - [results, visibleFieldNames, visibleFieldTypes] + [fields, visibleFieldNames, visibleFieldTypes] ); const fieldsCountStats = { visibleFieldsCount, totalFieldsCount }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/filter_fields.ts b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/filter_fields.ts similarity index 81% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/filter_fields.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/filter_fields.ts index 2c43d11c3d4476d..0120b17452558cd 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/filter_fields.ts +++ b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/filter_fields.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import { JOB_FIELD_TYPES } from '../../../../common'; import type { FileBasedFieldVisConfig, FileBasedUnknownFieldVisConfig, -} from '../../../stats_table/types/field_vis_config'; +} from '../stats_table/types/field_vis_config'; export function filterFields( fields: Array, @@ -32,6 +32,6 @@ export function filterFields( return { filteredFields: items, visibleFieldsCount: items.length, - visibleMetricsCount: items.filter((d) => d.type === ML_JOB_FIELD_TYPES.NUMBER).length, + visibleMetricsCount: items.filter((d) => d.type === JOB_FIELD_TYPES.NUMBER).length, }; } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/get_field_names.ts b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/get_field_names.ts similarity index 75% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/get_field_names.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/get_field_names.ts index d1cb361a84a729e..83c517dfe965edb 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/get_field_names.ts +++ b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/get_field_names.ts @@ -6,10 +6,10 @@ */ import { difference } from 'lodash'; -import type { FindFileStructureResponse } from '../../../../../../../file_upload/common'; -import { MlJobFieldType } from '../../../../../../common/types/field_types'; -import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; -import { ES_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/common'; +import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/common'; +import type { FindFileStructureResponse } from '../../../../../file_upload/common'; +import type { JobFieldType } from '../../../../common'; +import { JOB_FIELD_TYPES } from '../../../../common'; export function getFieldNames(results: FindFileStructureResponse) { const { mappings, field_stats: fieldStats, column_names: columnNames } = results; @@ -34,7 +34,7 @@ export function getFieldNames(results: FindFileStructureResponse) { return tempFields; } -export function getSupportedFieldType(type: string): MlJobFieldType { +export function getSupportedFieldType(type: string): JobFieldType { switch (type) { case ES_FIELD_TYPES.FLOAT: case ES_FIELD_TYPES.HALF_FLOAT: @@ -44,13 +44,13 @@ export function getSupportedFieldType(type: string): MlJobFieldType { case ES_FIELD_TYPES.LONG: case ES_FIELD_TYPES.SHORT: case ES_FIELD_TYPES.UNSIGNED_LONG: - return ML_JOB_FIELD_TYPES.NUMBER; + return JOB_FIELD_TYPES.NUMBER; case ES_FIELD_TYPES.DATE: case ES_FIELD_TYPES.DATE_NANOS: - return ML_JOB_FIELD_TYPES.DATE; + return JOB_FIELD_TYPES.DATE; default: - return type as MlJobFieldType; + return type as JobFieldType; } } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/fields_stats_grid/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/_file_contents.scss b/x-pack/plugins/file_data_visualizer/public/application/components/file_contents/_file_contents.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/_file_contents.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/file_contents/_file_contents.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/file_contents/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/_index.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/file_contents/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/file_contents/file_contents.tsx similarity index 78% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/file_contents/file_contents.tsx index 3de8e5851183da4..fa54cf9cbc05c0a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/file_contents/file_contents.tsx @@ -10,7 +10,7 @@ import React, { FC } from 'react'; import { EuiTitle, EuiSpacer } from '@elastic/eui'; -import { MLJobEditor, ML_EDITOR_MODE } from '../../../../jobs/jobs_list/components/ml_job_editor'; +import { JsonEditor, EDITOR_MODE } from '../json_editor'; interface Props { data: string; @@ -19,9 +19,9 @@ interface Props { } export const FileContents: FC = ({ data, format, numberOfLines }) => { - let mode = ML_EDITOR_MODE.TEXT; - if (format === ML_EDITOR_MODE.JSON) { - mode = ML_EDITOR_MODE.JSON; + let mode = EDITOR_MODE.TEXT; + if (format === EDITOR_MODE.JSON) { + mode = EDITOR_MODE.JSON; } const formattedData = limitByNumberOfLines(data, numberOfLines); @@ -31,7 +31,7 @@ export const FileContents: FC = ({ data, format, numberOfLines }) => {

@@ -39,7 +39,7 @@ export const FileContents: FC = ({ data, format, numberOfLines }) => {
= ({ data, format, numberOfLines }) => { - ), @@ -345,9 +345,10 @@ export class FileDataVisualizerView extends Component { fileContents={fileContents} data={data} indexPatterns={this.props.indexPatterns} - kibanaConfig={this.props.kibanaConfig} showBottomBar={this.showBottomBar} hideBottomBar={this.hideBottomBar} + savedObjectsClient={this.savedObjectsClient} + fileUpload={this.props.fileUpload} /> {bottomBarVisible && ( diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_error_callouts.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/file_datavisualizer_view/file_error_callouts.tsx similarity index 79% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_error_callouts.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/file_datavisualizer_view/file_error_callouts.tsx index 0fa7de4732c3910..b932dee35ebb84e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_error_callouts.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/file_datavisualizer_view/file_error_callouts.tsx @@ -11,8 +11,8 @@ import React, { FC } from 'react'; import { EuiCallOut, EuiSpacer, EuiButtonEmpty, EuiHorizontalRule } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { ErrorResponse } from '../../../../../../common/types/errors'; -import { FILE_SIZE_DISPLAY_FORMAT } from '../../../../../../../file_upload/public'; +import { FILE_SIZE_DISPLAY_FORMAT } from '../../../../common'; +import { FindFileStructureErrorResponse } from '../../../../../file_upload/common'; interface FileTooLargeProps { fileSize: number; @@ -31,7 +31,7 @@ export const FileTooLarge: FC = ({ fileSize, maxFileSize }) = errorText = (

= ({ fileSize, maxFileSize }) = errorText = (

= ({ fileSize, maxFileSize }) = } @@ -76,7 +76,7 @@ export const FileTooLarge: FC = ({ fileSize, maxFileSize }) = }; interface FileCouldNotBeReadProps { - error: ErrorResponse; + error: FindFileStructureErrorResponse; loaded: boolean; showEditFlyout(): void; } @@ -92,7 +92,7 @@ export const FileCouldNotBeRead: FC = ({ } @@ -103,13 +103,13 @@ export const FileCouldNotBeRead: FC = ({ {loaded === false && ( <>
@@ -122,7 +122,7 @@ export const FileCouldNotBeRead: FC = ({ <> @@ -132,11 +132,11 @@ export const FileCouldNotBeRead: FC = ({ ); }; -export const Explanation: FC<{ error: ErrorResponse }> = ({ error }) => { +export const Explanation: FC<{ error: FindFileStructureErrorResponse }> = ({ error }) => { if (!error?.body?.attributes?.body?.error?.suppressed?.length) { return null; } - const reason: string = error.body.attributes.body.error.suppressed[0].reason; + const reason = error.body.attributes.body.error.suppressed[0].reason; return ( <> diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/index.js b/x-pack/plugins/file_data_visualizer/public/application/components/file_datavisualizer_view/index.js similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/index.js rename to x-pack/plugins/file_data_visualizer/public/application/components/file_datavisualizer_view/index.js diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config.ts b/x-pack/plugins/file_data_visualizer/public/application/components/filebeat_config_flyout/filebeat_config.ts similarity index 91% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/filebeat_config_flyout/filebeat_config.ts index 2254110432bdba2..1cbb177c8644242 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config.ts +++ b/x-pack/plugins/file_data_visualizer/public/application/components/filebeat_config_flyout/filebeat_config.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; +import { FindFileStructureResponse } from '../../../../../file_upload/common'; export function createFilebeatConfig( index: string, @@ -36,7 +36,7 @@ export function createFilebeatConfig( } function getPaths() { - const txt = i18n.translate('xpack.ml.fileDatavisualizer.fileBeatConfig.paths', { + const txt = i18n.translate('xpack.fileDataVisualizer.fileBeatConfig.paths', { defaultMessage: 'add path to your files here', }); return [' paths:', ` - '<${txt}>'`]; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/filebeat_config_flyout/filebeat_config_flyout.tsx similarity index 83% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/filebeat_config_flyout/filebeat_config_flyout.tsx index c3b53d44300873e..a5d05bb06f78e84 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/filebeat_config_flyout/filebeat_config_flyout.tsx @@ -22,8 +22,8 @@ import { EuiCopy, } from '@elastic/eui'; import { createFilebeatConfig } from './filebeat_config'; -import { useMlKibana } from '../../../../contexts/kibana'; -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; +import { useFileDataVisualizerKibana } from '../../kibana_context'; // copy context? +import { FindFileStructureResponse } from '../../../../../file_upload/common'; export enum EDITOR_MODE { HIDDEN, @@ -48,7 +48,7 @@ export const FilebeatConfigFlyout: FC = ({ const [username, setUsername] = useState(null); const { services: { security }, - } = useMlKibana(); + } = useFileDataVisualizerKibana(); useEffect(() => { if (security !== undefined) { @@ -56,12 +56,12 @@ export const FilebeatConfigFlyout: FC = ({ setUsername(user.username === undefined ? null : user.username); }); } - }, []); + }, [security]); useEffect(() => { const config = createFilebeatConfig(index, results, ingestPipelineId, username); setFileBeatConfig(config); - }, [username]); + }, [username, index, ingestPipelineId, results]); return ( @@ -75,7 +75,7 @@ export const FilebeatConfigFlyout: FC = ({ @@ -85,7 +85,7 @@ export const FilebeatConfigFlyout: FC = ({ {(copy) => ( @@ -108,7 +108,7 @@ const Contents: FC<{

@@ -116,14 +116,14 @@ const Contents: FC<{

{index} }} />

filebeat.yml }} /> @@ -137,7 +137,7 @@ const Contents: FC<{

{username === null ? ( {''}, @@ -145,7 +145,7 @@ const Contents: FC<{ /> ) : ( {username}, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/filebeat_config_flyout/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/filebeat_config_flyout/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/import_errors/errors.tsx similarity index 81% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/import_errors/errors.tsx index 37e90b5f5753b59..5a6f78a1a306856 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/import_errors/errors.tsx @@ -38,56 +38,56 @@ function title(statuses: Statuses) { case statuses.readStatus: return ( ); case statuses.parseJSONStatus: return ( ); case statuses.indexCreatedStatus: return ( ); case statuses.ingestPipelineCreatedStatus: return ( ); case statuses.uploadStatus: return ( ); case statuses.indexPatternCreatedStatus: return ( ); case statuses.permissionCheckStatus: return ( ); default: return ( ); @@ -105,7 +105,7 @@ const ImportError: FC<{ error: any }> = ({ error }) => { id="more" buttonContent={ } @@ -151,7 +151,7 @@ function toString(error: any): ImportError { } return { - msg: i18n.translate('xpack.ml.fileDatavisualizer.importErrors.unknownErrorMessage', { + msg: i18n.translate('xpack.fileDataVisualizer.importErrors.unknownErrorMessage', { defaultMessage: 'Unknown error', }), }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/import_errors/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/import_errors/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/import_progress/import_progress.tsx similarity index 81% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/import_progress/import_progress.tsx index 40577a761cb03d2..8296a4885bf2c50 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/import_progress/import_progress.tsx @@ -80,31 +80,31 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { } let processFileTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.processFileTitle', + 'xpack.fileDataVisualizer.importProgress.processFileTitle', { defaultMessage: 'Process file', } ); let createIndexTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.createIndexTitle', + 'xpack.fileDataVisualizer.importProgress.createIndexTitle', { defaultMessage: 'Create index', } ); let createIngestPipelineTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.createIngestPipelineTitle', + 'xpack.fileDataVisualizer.importProgress.createIngestPipelineTitle', { defaultMessage: 'Create ingest pipeline', } ); let uploadingDataTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.uploadDataTitle', + 'xpack.fileDataVisualizer.importProgress.uploadDataTitle', { defaultMessage: 'Upload data', } ); let createIndexPatternTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.createIndexPatternTitle', + 'xpack.fileDataVisualizer.importProgress.createIndexPatternTitle', { defaultMessage: 'Create index pattern', } @@ -113,7 +113,7 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { const creatingIndexStatus = (

@@ -122,7 +122,7 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { const creatingIndexAndIngestPipelineStatus = (

@@ -130,7 +130,7 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { if (completedStep >= 0) { processFileTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.processingFileTitle', + 'xpack.fileDataVisualizer.importProgress.processingFileTitle', { defaultMessage: 'Processing file', } @@ -138,7 +138,7 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { statusInfo = (

@@ -146,13 +146,13 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { } if (completedStep >= 1) { processFileTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.fileProcessedTitle', + 'xpack.fileDataVisualizer.importProgress.fileProcessedTitle', { defaultMessage: 'File processed', } ); createIndexTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.creatingIndexTitle', + 'xpack.fileDataVisualizer.importProgress.creatingIndexTitle', { defaultMessage: 'Creating index', } @@ -161,14 +161,11 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { createPipeline === true ? creatingIndexAndIngestPipelineStatus : creatingIndexStatus; } if (completedStep >= 2) { - createIndexTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.indexCreatedTitle', - { - defaultMessage: 'Index created', - } - ); + createIndexTitle = i18n.translate('xpack.fileDataVisualizer.importProgress.indexCreatedTitle', { + defaultMessage: 'Index created', + }); createIngestPipelineTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.creatingIngestPipelineTitle', + 'xpack.fileDataVisualizer.importProgress.creatingIngestPipelineTitle', { defaultMessage: 'Creating ingest pipeline', } @@ -178,13 +175,13 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { } if (completedStep >= 3) { createIngestPipelineTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.ingestPipelineCreatedTitle', + 'xpack.fileDataVisualizer.importProgress.ingestPipelineCreatedTitle', { defaultMessage: 'Ingest pipeline created', } ); uploadingDataTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.uploadingDataTitle', + 'xpack.fileDataVisualizer.importProgress.uploadingDataTitle', { defaultMessage: 'Uploading data', } @@ -193,14 +190,14 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { } if (completedStep >= 4) { uploadingDataTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.dataUploadedTitle', + 'xpack.fileDataVisualizer.importProgress.dataUploadedTitle', { defaultMessage: 'Data uploaded', } ); if (createIndexPattern === true) { createIndexPatternTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.creatingIndexPatternTitle', + 'xpack.fileDataVisualizer.importProgress.creatingIndexPatternTitle', { defaultMessage: 'Creating index pattern', } @@ -208,7 +205,7 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { statusInfo = (

@@ -219,7 +216,7 @@ export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { } if (completedStep >= 5) { createIndexPatternTitle = i18n.translate( - 'xpack.ml.fileDatavisualizer.importProgress.indexPatternCreatedTitle', + 'xpack.fileDataVisualizer.importProgress.indexPatternCreatedTitle', { defaultMessage: 'Index pattern created', } @@ -293,7 +290,7 @@ const UploadFunctionProgress: FC<{ progress: number }> = ({ progress }) => {

diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/import_progress/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/import_progress/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/import_settings/advanced.tsx similarity index 84% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/import_settings/advanced.tsx index 5765c5de6c61b21..acb6415e93f9b52 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/import_settings/advanced.tsx @@ -19,8 +19,8 @@ import { } from '@elastic/eui'; import { CombinedField, CombinedFieldsForm } from '../combined_fields'; -import { MLJobEditor, ML_EDITOR_MODE } from '../../../../jobs/jobs_list/components/ml_job_editor'; -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; +import { JsonEditor, EDITOR_MODE } from '../json_editor'; +import { FindFileStructureResponse } from '../../../../../file_upload/common'; const EDITOR_HEIGHT = '300px'; interface Props { @@ -69,7 +69,7 @@ export const AdvancedSettings: FC = ({ } @@ -78,7 +78,7 @@ export const AdvancedSettings: FC = ({ > = ({ onChange={onIndexChange} isInvalid={indexNameError !== ''} aria-label={i18n.translate( - 'xpack.ml.fileDatavisualizer.advancedImportSettings.indexNameAriaLabel', + 'xpack.fileDataVisualizer.advancedImportSettings.indexNameAriaLabel', { defaultMessage: 'Index name, required field', } @@ -102,7 +102,7 @@ export const AdvancedSettings: FC = ({ id="createIndexPattern" label={ } @@ -116,7 +116,7 @@ export const AdvancedSettings: FC = ({ } @@ -184,14 +184,14 @@ const IndexSettings: FC = ({ initialized, data, onChange }) => } fullWidth > - = ({ initialized, data, onChange }) => { } fullWidth > - = ({ initialized, data, onChange }) => } fullWidth > - = ({ const tabs = [ { id: 'simple-settings', - name: i18n.translate('xpack.ml.fileDatavisualizer.importSettings.simpleTabName', { + name: i18n.translate('xpack.fileDataVisualizer.importSettings.simpleTabName', { defaultMessage: 'Simple', }), content: ( @@ -80,7 +80,7 @@ export const ImportSettings: FC = ({ }, { id: 'advanced-settings', - name: i18n.translate('xpack.ml.fileDatavisualizer.importSettings.advancedTabName', { + name: i18n.translate('xpack.fileDataVisualizer.importSettings.advancedTabName', { defaultMessage: 'Advanced', }), content: ( diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/import_settings/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/import_settings/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/import_settings/simple.tsx similarity index 86% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/import_settings/simple.tsx index daa360f0e1af0f8..2751b37cd3256a0 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/import_settings/simple.tsx @@ -36,7 +36,7 @@ export const SimpleSettings: FC = ({ } @@ -45,7 +45,7 @@ export const SimpleSettings: FC = ({ > = ({ onChange={onIndexChange} isInvalid={indexNameError !== ''} aria-label={i18n.translate( - 'xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameAriaLabel', + 'xpack.fileDataVisualizer.simpleImportSettings.indexNameAriaLabel', { defaultMessage: 'Index name, required field', } @@ -70,7 +70,7 @@ export const SimpleSettings: FC = ({ id="createIndexPattern" label={ } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/_import_sumary.scss b/x-pack/plugins/file_data_visualizer/public/application/components/import_summary/_import_sumary.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/_import_sumary.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/import_summary/_import_sumary.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/import_summary/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/_index.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/import_summary/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/failures.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/import_summary/failures.tsx similarity index 95% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/failures.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/import_summary/failures.tsx index 498320b1b792dfe..c8f62021b7baea0 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/failures.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/import_summary/failures.tsx @@ -51,7 +51,7 @@ export class Failures extends Component { id="failureList" buttonContent={ } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/import_summary/import_summary.tsx similarity index 84% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/import_summary/import_summary.tsx index 7fa71193ee51614..f981b1fdf9f234f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/import_summary/import_summary.tsx @@ -45,7 +45,7 @@ export const ImportSummary: FC = ({ } @@ -62,7 +62,7 @@ export const ImportSummary: FC = ({ } @@ -71,7 +71,7 @@ export const ImportSummary: FC = ({ >

), @@ -111,7 +111,7 @@ function createDisplayItems( { title: ( ), @@ -123,7 +123,7 @@ function createDisplayItems( items.splice(1, 0, { title: ( ), @@ -135,7 +135,7 @@ function createDisplayItems( items.splice(1, 0, { title: ( ), @@ -147,7 +147,7 @@ function createDisplayItems( items.push({ title: ( ), diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/import_summary/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/import_summary/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/plugins/file_data_visualizer/public/application/components/import_view/import_view.js similarity index 92% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js rename to x-pack/plugins/file_data_visualizer/public/application/components/import_view/import_view.js index 04175f46c920171..0eaba4c033910b4 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js +++ b/x-pack/plugins/file_data_visualizer/public/application/components/import_view/import_view.js @@ -20,7 +20,6 @@ import { import { i18n } from '@kbn/i18n'; import { debounce } from 'lodash'; -import { getFileUpload } from '../../../../util/dependency_cache'; import { ResultsLinks } from '../results_links'; import { FilebeatConfigFlyout } from '../filebeat_config_flyout'; import { ImportProgress, IMPORT_STATUS } from '../import_progress'; @@ -33,8 +32,6 @@ import { getDefaultCombinedFields, } from '../combined_fields'; import { ExperimentalBadge } from '../experimental_badge'; -import { getIndexPatternNames, loadIndexPatterns } from '../../../../util/index_utils'; -import { ml } from '../../../../services/ml_api_service'; const DEFAULT_TIME_FIELD = '@timestamp'; const DEFAULT_INDEX_SETTINGS = { number_of_shards: 1 }; @@ -81,6 +78,7 @@ export class ImportView extends Component { super(props); this.state = getDefaultState(DEFAULT_STATE, this.props.results); + this.savedObjectsClient = props.savedObjectsClient; } componentDidMount() { @@ -100,7 +98,7 @@ export class ImportView extends Component { // TODO - sort this function out. it's a mess async import() { - const { data, results, indexPatterns, kibanaConfig, showBottomBar } = this.props; + const { data, results, indexPatterns, showBottomBar, fileUpload } = this.props; const { format } = results; let { timeFieldName } = this.state; @@ -124,14 +122,14 @@ export class ImportView extends Component { async () => { // check to see if the user has permission to create and ingest data into the specified index if ( - (await getFileUpload().hasImportPermission({ + (await fileUpload.hasImportPermission({ checkCreateIndexPattern: createIndexPattern, checkHasManagePipeline: true, indexName: index, })) === false ) { errors.push( - i18n.translate('xpack.ml.fileDatavisualizer.importView.importPermissionError', { + i18n.translate('xpack.fileDataVisualizer.importView.importPermissionError', { defaultMessage: 'You do not have permission to create or import data into index {index}.', values: { @@ -171,7 +169,7 @@ export class ImportView extends Component { } catch (error) { success = false; const parseError = i18n.translate( - 'xpack.ml.fileDatavisualizer.importView.parseSettingsError', + 'xpack.fileDataVisualizer.importView.parseSettingsError', { defaultMessage: 'Error parsing settings:', } @@ -184,7 +182,7 @@ export class ImportView extends Component { } catch (error) { success = false; const parseError = i18n.translate( - 'xpack.ml.fileDatavisualizer.importView.parseMappingsError', + 'xpack.fileDataVisualizer.importView.parseMappingsError', { defaultMessage: 'Error parsing mappings:', } @@ -199,7 +197,7 @@ export class ImportView extends Component { } catch (error) { success = false; const parseError = i18n.translate( - 'xpack.ml.fileDatavisualizer.importView.parsePipelineError', + 'xpack.fileDataVisualizer.importView.parsePipelineError', { defaultMessage: 'Error parsing ingest pipeline:', } @@ -221,7 +219,7 @@ export class ImportView extends Component { } if (success) { - const importer = await getFileUpload().importerFactory(format, { + const importer = await fileUpload.importerFactory(format, { excludeLinesPattern: results.exclude_lines_pattern, multilineStartPattern: results.multiline_start_pattern, }); @@ -294,8 +292,7 @@ export class ImportView extends Component { const indexPatternResp = await createKibanaIndexPattern( indexPatternName, indexPatterns, - timeFieldName, - kibanaConfig + timeFieldName ); success = indexPatternResp.success; this.setState({ @@ -354,16 +351,15 @@ export class ImportView extends Component { return; } - const { exists } = await ml.checkIndexExists({ index }); + const exists = await this.props.fileUpload.checkIndexExists(index); const indexNameError = exists ? ( ) : ( isIndexNameValid(index) ); - this.setState({ checkingValidIndex: false, indexNameError }); }, 500); @@ -427,9 +423,19 @@ export class ImportView extends Component { }; async loadIndexPatternNames() { - await loadIndexPatterns(this.props.indexPatterns); - const indexPatternNames = getIndexPatternNames(); - this.setState({ indexPatternNames }); + try { + const indexPatternNames = ( + await this.savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title'], + perPage: 10000, + }) + ).savedObjects.map(({ attributes }) => attributes && attributes.title); + + this.setState({ indexPatternNames }); + } catch (error) { + console.error('failed to load index patterns', error); + } } render() { @@ -501,14 +507,14 @@ export class ImportView extends Component {

  } @@ -549,7 +555,7 @@ export class ImportView extends Component { data-test-subj="mlFileDataVisImportButton" > @@ -558,7 +564,7 @@ export class ImportView extends Component { {initialized === true && importing === false && ( @@ -690,7 +696,7 @@ function isIndexNameValid(name) { ) { return ( ); @@ -707,7 +713,7 @@ function isIndexPatternNameValid(name, indexPatternNames, index) { if (indexPatternNames.find((i) => i === name)) { return ( ); @@ -723,7 +729,7 @@ function isIndexPatternNameValid(name, indexPatternNames, index) { // name should match index return ( ); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/index.js b/x-pack/plugins/file_data_visualizer/public/application/components/import_view/index.js similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/index.js rename to x-pack/plugins/file_data_visualizer/public/application/components/import_view/index.js diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/json_editor/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/json_editor/index.ts new file mode 100644 index 000000000000000..641587e5ac73264 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/json_editor/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { EDITOR_MODE, JsonEditor } from './json_editor'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/json_editor/json_editor.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/json_editor/json_editor.tsx new file mode 100644 index 000000000000000..d429f8dada6ec10 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/json_editor/json_editor.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +import { EuiCodeEditor, EuiCodeEditorProps } from '@elastic/eui'; +import { expandLiteralStrings, XJsonMode } from '../../shared_imports'; + +export const EDITOR_MODE = { TEXT: 'text', JSON: 'json', XJSON: new XJsonMode() }; + +interface JobEditorProps { + value: string; + height?: string; + width?: string; + mode?: typeof EDITOR_MODE[keyof typeof EDITOR_MODE]; + readOnly?: boolean; + syntaxChecking?: boolean; + theme?: string; + onChange?: EuiCodeEditorProps['onChange']; +} +export const JsonEditor: FC = ({ + value, + height = '500px', + width = '100%', + mode = EDITOR_MODE.JSON, + readOnly = false, + syntaxChecking = true, + theme = 'textmate', + onChange = () => {}, +}) => { + if (mode === EDITOR_MODE.XJSON) { + value = expandLiteralStrings(value); + } + + return ( + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/multi_select_picker/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/multi_select_picker/index.ts new file mode 100644 index 000000000000000..9d32228e1c4bc92 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/multi_select_picker/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { MultiSelectPicker, Option } from './multi_select_picker'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/multi_select_picker/multi_select_picker.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/multi_select_picker/multi_select_picker.tsx new file mode 100644 index 000000000000000..2093b61a7ef4dcd --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/multi_select_picker/multi_select_picker.tsx @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFieldSearch, + EuiFilterButton, + EuiFilterGroup, + EuiFilterSelectItem, + EuiIcon, + EuiPopover, + EuiPopoverTitle, + EuiSpacer, +} from '@elastic/eui'; +import React, { FC, ReactNode, useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export interface Option { + name?: string | ReactNode; + value: string; + checked?: 'on' | 'off'; +} + +const NoFilterItems = () => { + return ( +
+
+ + +

+ +

+
+
+ ); +}; + +export const MultiSelectPicker: FC<{ + options: Option[]; + onChange?: (items: string[]) => void; + title?: string; + checkedOptions: string[]; + dataTestSubj: string; +}> = ({ options, onChange, title, checkedOptions, dataTestSubj }) => { + const [items, setItems] = useState(options); + const [searchTerm, setSearchTerm] = useState(''); + + useEffect(() => { + if (searchTerm === '') { + setItems(options); + } else { + const filteredOptions = options.filter((o) => o?.value?.includes(searchTerm)); + setItems(filteredOptions); + } + }, [options, searchTerm]); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onButtonClick = () => { + setIsPopoverOpen(!isPopoverOpen); + }; + + const closePopover = () => { + setIsPopoverOpen(false); + }; + + const handleOnChange = (index: number) => { + if (!items[index] || !Array.isArray(checkedOptions) || onChange === undefined) { + return; + } + const item = items[index]; + const foundIndex = checkedOptions.findIndex((fieldValue) => fieldValue === item.value); + if (foundIndex > -1) { + onChange(checkedOptions.filter((_, idx) => idx !== foundIndex)); + } else { + onChange([...checkedOptions, item.value]); + } + }; + + const button = ( + 0} + numActiveFilters={checkedOptions && checkedOptions.length} + > + {title} + + ); + + return ( + + + + setSearchTerm(e.target.value)} + data-test-subj={`${dataTestSubj}-searchInput`} + /> + +
+ {Array.isArray(items) && items.length > 0 ? ( + items.map((item, index) => { + const checked = + checkedOptions && + checkedOptions.findIndex((fieldValue) => fieldValue === item.value) > -1; + + return ( + handleOnChange(index)} + style={{ flexDirection: 'row' }} + data-test-subj={`${dataTestSubj}-option-${item.value}${ + checked ? '-checked' : '' + }`} + > + {item.name ?? item.value} + + ); + }) + ) : ( + + )} +
+
+
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/results_links/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/results_links/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/results_links/results_links.tsx similarity index 61% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/results_links/results_links.tsx index 90b8fb4ac0cbbd2..03dc06d836bbc21 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/results_links/results_links.tsx @@ -9,20 +9,14 @@ import React, { FC, useState, useEffect } from 'react'; import moment from 'moment'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui'; -import { ml } from '../../../../services/ml_api_service'; -import { isFullLicense } from '../../../../license'; -import { checkPermission } from '../../../../capabilities/check_capabilities'; -import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes'; -import { useMlKibana, useMlUrlGenerator } from '../../../../contexts/kibana'; -import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator'; -import { MlCommonGlobalState } from '../../../../../../common/types/ml_url_generator'; import { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState, -} from '../../../../../../../../../src/plugins/discover/public'; -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; - -const RECHECK_DELAY_MS = 3000; +} from '../../../../../../../src/plugins/discover/public'; +import { TimeRange, RefreshInterval } from '../../../../../../../src/plugins/data/public'; +import { FindFileStructureResponse } from '../../../../../file_upload/common'; +import type { FileUploadPluginStart } from '../../../../../file_upload/public'; +import { useFileDataVisualizerKibana } from '../../kibana_context'; interface Props { fieldStats: FindFileStructureResponse['field_stats']; @@ -33,6 +27,13 @@ interface Props { showFilebeatFlyout(): void; } +interface GlobalState { + time?: TimeRange; + refreshInterval?: RefreshInterval; +} + +const RECHECK_DELAY_MS = 3000; + export const ResultsLinks: FC = ({ fieldStats, index, @@ -41,20 +42,19 @@ export const ResultsLinks: FC = ({ createIndexPattern, showFilebeatFlyout, }) => { + const { + services: { fileUpload }, + } = useFileDataVisualizerKibana(); + const [duration, setDuration] = useState({ from: 'now-30m', to: 'now', }); - const [showCreateJobLink, setShowCreateJobLink] = useState(false); - const [globalState, setGlobalState] = useState(); + const [globalState, setGlobalState] = useState(); const [discoverLink, setDiscoverLink] = useState(''); const [indexManagementLink, setIndexManagementLink] = useState(''); const [indexPatternManagementLink, setIndexPatternManagementLink] = useState(''); - const [dataVisualizerLink, setDataVisualizerLink] = useState(''); - const [createJobsSelectTypePage, setCreateJobsSelectTypePage] = useState(''); - - const mlUrlGenerator = useMlUrlGenerator(); const { services: { @@ -63,7 +63,7 @@ export const ResultsLinks: FC = ({ urlGenerators: { getUrlGenerator }, }, }, - } = useMlKibana(); + } = useFileDataVisualizerKibana(); useEffect(() => { let unmounted = false; @@ -98,34 +98,7 @@ export const ResultsLinks: FC = ({ } }; - const getDataVisualizerLink = async (): Promise => { - const _dataVisualizerLink = await mlUrlGenerator.createUrl({ - page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER, - pageState: { - index: indexPatternId, - globalState, - }, - }); - if (!unmounted) { - setDataVisualizerLink(_dataVisualizerLink); - } - }; - const getADCreateJobsSelectTypePage = async (): Promise => { - const _createJobsSelectTypePage = await mlUrlGenerator.createUrl({ - page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE, - pageState: { - index: indexPatternId, - globalState, - }, - }); - if (!unmounted) { - setCreateJobsSelectTypePage(_createJobsSelectTypePage); - } - }; - getDiscoverUrl(); - getDataVisualizerLink(); - getADCreateJobsSelectTypePage(); if (!unmounted) { setIndexManagementLink( @@ -141,15 +114,16 @@ export const ResultsLinks: FC = ({ return () => { unmounted = true; }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [indexPatternId, getUrlGenerator, JSON.stringify(globalState)]); useEffect(() => { - setShowCreateJobLink(checkPermission('canCreateJob') && mlNodesAvailable()); updateTimeValues(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { - const _globalState: MlCommonGlobalState = { + const _globalState: GlobalState = { time: { from: duration.from, to: duration.to, @@ -176,7 +150,7 @@ export const ResultsLinks: FC = ({ async function updateTimeValues(recheck = true) { if (timeFieldName !== undefined) { - const { from, to } = await getFullTimeRange(index, timeFieldName); + const { from, to } = await getFullTimeRange(index, timeFieldName, fileUpload); setDuration({ from: from === null ? duration.from : from, to: to === null ? duration.to : to, @@ -202,7 +176,7 @@ export const ResultsLinks: FC = ({ icon={} title={ } @@ -212,49 +186,13 @@ export const ResultsLinks: FC = ({ )} - {isFullLicense() === true && - timeFieldName !== undefined && - showCreateJobLink && - createIndexPattern && - createJobsSelectTypePage && ( - - } - title={ - - } - description="" - href={createJobsSelectTypePage} - /> - - )} - - {createIndexPattern && dataVisualizerLink && ( - - } - title={ - - } - description="" - href={dataVisualizerLink} - /> - - )} - {indexManagementLink && ( } title={ } @@ -270,7 +208,7 @@ export const ResultsLinks: FC = ({ icon={} title={ } @@ -284,7 +222,7 @@ export const ResultsLinks: FC = ({ icon={} title={ } @@ -296,13 +234,13 @@ export const ResultsLinks: FC = ({ ); }; -async function getFullTimeRange(index: string, timeFieldName: string) { +async function getFullTimeRange( + index: string, + timeFieldName: string, + { getTimeFieldRange }: FileUploadPluginStart +) { const query = { bool: { must: [{ query_string: { analyze_wildcard: true, query: '*' } }] } }; - const resp = await ml.getTimeFieldRange({ - index, - timeFieldName, - query, - }); + const resp = await getTimeFieldRange(index, query, timeFieldName); return { from: moment(resp.start.epoch).toISOString(), diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/results_view/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/_index.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/results_view/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/_results_view.scss b/x-pack/plugins/file_data_visualizer/public/application/components/results_view/_results_view.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/_results_view.scss rename to x-pack/plugins/file_data_visualizer/public/application/components/results_view/_results_view.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/results_view/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/results_view/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/results_view/results_view.tsx similarity index 89% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx rename to x-pack/plugins/file_data_visualizer/public/application/components/results_view/results_view.tsx index 7431bfd4295e4e5..e2d21f242e4ef84 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx +++ b/x-pack/plugins/file_data_visualizer/public/application/components/results_view/results_view.tsx @@ -20,7 +20,7 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { FindFileStructureResponse } from '../../../../../../../file_upload/common'; +import { FindFileStructureResponse } from '../../../../../file_upload/common'; import { FileContents } from '../file_contents'; import { AnalysisSummary } from '../analysis_summary'; @@ -72,7 +72,7 @@ export const ResultsView: FC = ({ showEditFlyout()} disabled={disableButtons}> @@ -80,7 +80,7 @@ export const ResultsView: FC = ({ showExplanationFlyout()} disabled={disableButtons}> @@ -94,7 +94,7 @@ export const ResultsView: FC = ({

diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/_field_data_row.scss b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/_field_data_row.scss new file mode 100644 index 000000000000000..944c31da8cab7aa --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/_field_data_row.scss @@ -0,0 +1,86 @@ +.fieldDataCard { + height: 420px; + box-shadow: none; + border-color: $euiBorderColor; + + // Note the names of these styles need to match the type of the field they are displaying. + .boolean { + color: $euiColorVis5; + border-color: $euiColorVis5; + } + + .date { + color: $euiColorVis7; + border-color: $euiColorVis7; + } + + .document_count { + color: $euiColorVis2; + border-color: $euiColorVis2; + } + + .geo_point { + color: $euiColorVis8; + border-color: $euiColorVis8; + } + + .ip { + color: $euiColorVis3; + border-color: $euiColorVis3; + } + + .keyword { + color: $euiColorVis0; + border-color: $euiColorVis0; + } + + .number { + color: $euiColorVis1; + border-color: $euiColorVis1; + } + + .text { + color: $euiColorVis9; + border-color: $euiColorVis9; + } + + .type-other, + .unknown { + color: $euiColorVis6; + border-color: $euiColorVis6; + } + + .fieldDataCard__content { + @include euiFontSizeS; + height: 385px; + overflow: hidden; + } + + .fieldDataCard__codeContent { + @include euiCodeFont; + } + + .fieldDataCard__geoContent { + z-index: auto; + flex: 1; + display: flex; + flex-direction: column; + height: 100%; + position: relative; + .embPanel__content { + display: flex; + flex: 1 1 100%; + z-index: 1; + min-height: 0; // Absolute must for Firefox to scroll contents + } + } + + .fieldDataCard__stats { + padding: $euiSizeS $euiSizeS 0 $euiSizeS; + text-align: center; + } + + .fieldDataCard__valuesTitle { + text-transform: uppercase; + } +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/_index.scss new file mode 100644 index 000000000000000..d317d324bae90c6 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/_index.scss @@ -0,0 +1,56 @@ +@import 'components/field_data_expanded_row/index'; +@import 'components/field_count_stats/index'; +@import 'components/field_data_row/index'; + +.dataVisualizerFieldExpandedRow { + padding-left: $euiSize * 4; + width: 100%; + + .fieldDataCard__valuesTitle { + text-transform: uppercase; + text-align: left; + color: $euiColorDarkShade; + font-weight: bold; + padding-bottom: $euiSizeS; + } + + .fieldDataCard__codeContent { + @include euiCodeFont; + } +} + +.dataVisualizer { + .euiTableRow > .euiTableRowCell { + border-bottom: 0; + border-top: $euiBorderThin; + + } + .euiTableRow-isExpandedRow { + + .euiTableRowCell { + background-color: $euiColorEmptyShade !important; + border-top: 0; + border-bottom: $euiBorderThin; + &:hover { + background-color: $euiColorEmptyShade !important; + } + } + } + .dataVisualizerSummaryTable { + max-width: 350px; + min-width: 250px; + .euiTableRow > .euiTableRowCell { + border-bottom: 0; + } + .euiTableHeaderCell { + display: none; + } + } + .dataVisualizerSummaryTableWrapper { + max-width: 300px; + } + .dataVisualizerMapWrapper { + min-height: 300px; + min-width: 600px; + } +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/expanded_row_field_header/expanded_row_field_header.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/expanded_row_field_header/expanded_row_field_header.tsx new file mode 100644 index 000000000000000..7279bceb8be93d9 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/expanded_row_field_header/expanded_row_field_header.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiText } from '@elastic/eui'; +import React from 'react'; + +export const ExpandedRowFieldHeader = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/expanded_row_field_header/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/expanded_row_field_header/index.ts new file mode 100644 index 000000000000000..a92fa7f1e0659e1 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/expanded_row_field_header/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ExpandedRowFieldHeader } from './expanded_row_field_header'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/_index.scss new file mode 100644 index 000000000000000..e44082c90ba32e4 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/_index.scss @@ -0,0 +1,3 @@ +.dataVisualizerFieldCountContainer { + max-width: 300px; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/index.ts new file mode 100644 index 000000000000000..d841ee2959f6201 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { TotalFieldsCount, TotalFieldsCountProps, TotalFieldsStats } from './total_fields_count'; +export { + MetricFieldsCount, + MetricFieldsCountProps, + MetricFieldsStats, +} from './metric_fields_count'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/metric_fields_count.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/metric_fields_count.tsx new file mode 100644 index 000000000000000..93582a7cef9edf5 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/metric_fields_count.tsx @@ -0,0 +1,68 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC } from 'react'; + +export interface MetricFieldsStats { + visibleMetricsCount: number; + totalMetricFieldsCount: number; +} +export interface MetricFieldsCountProps { + metricsStats?: MetricFieldsStats; +} + +export const MetricFieldsCount: FC = ({ metricsStats }) => { + if ( + !metricsStats || + metricsStats.visibleMetricsCount === undefined || + metricsStats.totalMetricFieldsCount === undefined + ) + return null; + return ( + <> + {metricsStats && ( + + + +
+ +
+
+
+ + + {metricsStats.visibleMetricsCount} + + + + + + + +
+ )} + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/total_fields_count.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/total_fields_count.tsx new file mode 100644 index 000000000000000..9d554c7025d8090 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_count_stats/total_fields_count.tsx @@ -0,0 +1,67 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC } from 'react'; + +export interface TotalFieldsStats { + visibleFieldsCount: number; + totalFieldsCount: number; +} + +export interface TotalFieldsCountProps { + fieldsCountStats?: TotalFieldsStats; +} + +export const TotalFieldsCount: FC = ({ fieldsCountStats }) => { + if ( + !fieldsCountStats || + fieldsCountStats.visibleFieldsCount === undefined || + fieldsCountStats.totalFieldsCount === undefined + ) + return null; + + return ( + + + +
+ +
+
+
+ + + + {fieldsCountStats.visibleFieldsCount} + + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/_index.scss new file mode 100644 index 000000000000000..b878bf0dcc0f60e --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/_index.scss @@ -0,0 +1,7 @@ +@import 'number_content'; + +.dataVisualizerExpandedRow { + @include euiBreakpoint('xs', 's', 'm') { + flex-direction: column; + } +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/_number_content.scss b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/_number_content.scss new file mode 100644 index 000000000000000..1f52b0763cdd387 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/_number_content.scss @@ -0,0 +1,4 @@ +.metricDistributionChartContainer { + padding-top: $euiSizeXS; + width: 100%; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/boolean_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/boolean_content.tsx new file mode 100644 index 000000000000000..7c9ddcdab29c8ed --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/boolean_content.tsx @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, ReactNode, useMemo } from 'react'; +import { EuiBasicTable, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { Axis, BarSeries, Chart, Settings } from '@elastic/charts'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { getTFPercentage } from '../../utils'; +import { roundToDecimalPlace } from '../../../utils'; +import { useDataVizChartTheme } from '../../hooks'; +import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; + +function getPercentLabel(value: number): string { + if (value === 0) { + return '0%'; + } + if (value >= 0.1) { + return `${roundToDecimalPlace(value)}%`; + } else { + return '< 0.1%'; + } +} + +function getFormattedValue(value: number, totalCount: number): string { + const percentage = (value / totalCount) * 100; + return `${value} (${getPercentLabel(percentage)})`; +} + +const BOOLEAN_DISTRIBUTION_CHART_HEIGHT = 100; + +export const BooleanContent: FC = ({ config }) => { + const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; + const formattedPercentages = useMemo(() => getTFPercentage(config), [config]); + const theme = useDataVizChartTheme(); + if (!formattedPercentages) return null; + + const { trueCount, falseCount, count } = formattedPercentages; + const summaryTableItems = [ + { + function: 'true', + display: ( + + ), + value: getFormattedValue(trueCount, count), + }, + { + function: 'false', + display: ( + + ), + value: getFormattedValue(falseCount, count), + }, + ]; + const summaryTableColumns = [ + { + name: '', + render: (summaryItem: { display: ReactNode }) => summaryItem.display, + width: '75px', + }, + { + field: 'value', + name: '', + render: (v: string) => {v}, + }, + ]; + + const summaryTableTitle = i18n.translate( + 'xpack.fileDataVisualizer.fieldDataCardExpandedRow.booleanContent.summaryTableTitle', + { + defaultMessage: 'Summary', + } + ); + + return ( + + + + + {summaryTableTitle} + + + + + + + + + + + getFormattedValue(d, count)} + /> + + + + + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/date_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/date_content.tsx new file mode 100644 index 000000000000000..cf34417ad9bbd7c --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/date_content.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, ReactNode } from 'react'; +import { EuiBasicTable, EuiFlexItem } from '@elastic/eui'; +// @ts-ignore +import { formatDate } from '@elastic/eui/lib/services/format'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { i18n } from '@kbn/i18n'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; +const TIME_FORMAT = 'MMM D YYYY, HH:mm:ss.SSS'; +interface SummaryTableItem { + function: string; + display: ReactNode; + value: number | string | undefined | null; +} + +export const DateContent: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined) return null; + + const { earliest, latest } = stats; + + const summaryTableTitle = i18n.translate( + 'xpack.fileDataVisualizer.fieldDataCard.cardDate.summaryTableTitle', + { + defaultMessage: 'Summary', + } + ); + const summaryTableItems = [ + { + function: 'earliest', + display: ( + + ), + value: typeof earliest === 'string' ? earliest : formatDate(earliest, TIME_FORMAT), + }, + { + function: 'latest', + display: ( + + ), + value: typeof latest === 'string' ? latest : formatDate(latest, TIME_FORMAT), + }, + ]; + const summaryTableColumns = [ + { + name: '', + render: (summaryItem: { display: ReactNode }) => summaryItem.display, + width: '75px', + }, + { + field: 'value', + name: '', + render: (v: string) => {v}, + }, + ]; + + return ( + + + + {summaryTableTitle} + + className={'dataVisualizerSummaryTable'} + data-test-subj={'mlDateSummaryTable'} + compressed + items={summaryTableItems} + columns={summaryTableColumns} + tableCaption={summaryTableTitle} + tableLayout="auto" + /> + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/document_stats.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/document_stats.tsx new file mode 100644 index 000000000000000..f3ac0d94aa25514 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/document_stats.tsx @@ -0,0 +1,93 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC, ReactNode } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiBasicTable, EuiFlexItem } from '@elastic/eui'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { FieldDataRowProps } from '../../types'; +import { roundToDecimalPlace } from '../../../utils'; + +const metaTableColumns = [ + { + name: '', + render: (metaItem: { display: ReactNode }) => metaItem.display, + width: '75px', + }, + { + field: 'value', + name: '', + render: (v: string) => {v}, + }, +]; + +const metaTableTitle = i18n.translate( + 'xpack.fileDataVisualizer.fieldDataCardExpandedRow.documentStatsTable.metaTableTitle', + { + defaultMessage: 'Documents stats', + } +); + +export const DocumentStatsTable: FC = ({ config }) => { + if ( + config?.stats === undefined || + config.stats.cardinality === undefined || + config.stats.count === undefined || + config.stats.sampleCount === undefined + ) + return null; + const { cardinality, count, sampleCount } = config.stats; + const metaTableItems = [ + { + function: 'count', + display: ( + + ), + value: count, + }, + { + function: 'percentage', + display: ( + + ), + value: `${roundToDecimalPlace((count / sampleCount) * 100)}%`, + }, + { + function: 'distinctValues', + display: ( + + ), + value: cardinality, + }, + ]; + + return ( + + {metaTableTitle} + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/expanded_row_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/expanded_row_content.tsx new file mode 100644 index 000000000000000..a9f5dc6eaab1d71 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/expanded_row_content.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, ReactNode } from 'react'; +import { EuiFlexGroup } from '@elastic/eui'; + +interface Props { + children: ReactNode; + dataTestSubj: string; +} +export const ExpandedRowContent: FC = ({ children, dataTestSubj }) => { + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/index.ts new file mode 100644 index 000000000000000..c8db31146936de6 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { BooleanContent } from './boolean_content'; +export { DateContent } from './date_content'; +export { GeoPointContent } from '../../../expanded_row/geo_point_content/geo_point_content'; +export { KeywordContent } from './keyword_content'; +export { IpContent } from './ip_content'; +export { NumberContent } from './number_content'; +export { OtherContent } from './other_content'; +export { TextContent } from './text_content'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/ip_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/ip_content.tsx new file mode 100644 index 000000000000000..07adf3103b78ee0 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/ip_content.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { TopValues } from '../../../top_values'; +import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; + +export const IpContent: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined) return null; + const { count, sampleCount, cardinality } = stats; + if (count === undefined || sampleCount === undefined || cardinality === undefined) return null; + const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; + + return ( + + + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/keyword_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/keyword_content.tsx new file mode 100644 index 000000000000000..3f1a7aad5463f51 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/keyword_content.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { TopValues } from '../../../top_values'; +import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; + +export const KeywordContent: FC = ({ config }) => { + const { stats } = config; + const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/number_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/number_content.tsx new file mode 100644 index 000000000000000..e83eecb64d02e2a --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/number_content.tsx @@ -0,0 +1,154 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, ReactNode, useEffect, useState } from 'react'; +import { EuiBasicTable, EuiFlexItem, EuiText } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { kibanaFieldFormat, numberAsOrdinal } from '../../../utils'; +import { + MetricDistributionChart, + MetricDistributionChartData, + buildChartDataFromStats, +} from '../metric_distribution_chart'; +import { TopValues } from '../../../top_values'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; + +const METRIC_DISTRIBUTION_CHART_WIDTH = 325; +const METRIC_DISTRIBUTION_CHART_HEIGHT = 200; + +interface SummaryTableItem { + function: string; + display: ReactNode; + value: number | string | undefined | null; +} + +export const NumberContent: FC = ({ config }) => { + const { stats } = config; + + useEffect(() => { + const chartData = buildChartDataFromStats(stats, METRIC_DISTRIBUTION_CHART_WIDTH); + setDistributionChartData(chartData); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const defaultChartData: MetricDistributionChartData[] = []; + const [distributionChartData, setDistributionChartData] = useState(defaultChartData); + + if (stats === undefined) return null; + const { min, median, max, distribution } = stats; + const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; + + const summaryTableItems = [ + { + function: 'min', + display: ( + + ), + value: kibanaFieldFormat(min, fieldFormat), + }, + { + function: 'median', + display: ( + + ), + value: kibanaFieldFormat(median, fieldFormat), + }, + { + function: 'max', + display: ( + + ), + value: kibanaFieldFormat(max, fieldFormat), + }, + ]; + const summaryTableColumns = [ + { + name: '', + render: (summaryItem: { display: ReactNode }) => summaryItem.display, + width: '75px', + }, + { + field: 'value', + name: '', + render: (v: string) => {v}, + }, + ]; + + const summaryTableTitle = i18n.translate( + 'xpack.fileDataVisualizer.fieldDataCardExpandedRow.numberContent.summaryTableTitle', + { + defaultMessage: 'Summary', + } + ); + return ( + + + + {summaryTableTitle} + + className={'dataVisualizerSummaryTable'} + compressed + items={summaryTableItems} + columns={summaryTableColumns} + tableCaption={summaryTableTitle} + data-test-subj={'mlNumberSummaryTable'} + /> + + + {stats && ( + + )} + {distribution && ( + + + + + + + + + + + + + + + + + )} + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/other_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/other_content.tsx new file mode 100644 index 000000000000000..cb1605331551e82 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/other_content.tsx @@ -0,0 +1,28 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { EuiFlexItem } from '@elastic/eui'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExamplesList } from '../../../examples_list'; +import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; + +export const OtherContent: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined) return null; + return ( + + + {Array.isArray(stats.examples) && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/text_content.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/text_content.tsx new file mode 100644 index 000000000000000..b399f952b4d9dad --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_expanded_row/text_content.tsx @@ -0,0 +1,69 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, Fragment } from 'react'; +import { EuiCallOut, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExamplesList } from '../../../examples_list'; +import { ExpandedRowContent } from './expanded_row_content'; + +export const TextContent: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined) return null; + + const { examples } = stats; + if (examples === undefined) return null; + + const numExamples = examples.length; + + return ( + + + {numExamples > 0 && } + {numExamples === 0 && ( + + + + _source, + }} + /> + + + + copy_to, + sourceParam: _source, + includesParam: includes, + excludesParam: excludes, + }} + /> + + + )} + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/_index.scss b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/_index.scss new file mode 100644 index 000000000000000..3afa182560e1e40 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/_index.scss @@ -0,0 +1,3 @@ +.dataVisualizerColumnHeaderIcon { + max-width: $euiSizeM; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/boolean_content_preview.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/boolean_content_preview.tsx new file mode 100644 index 000000000000000..c6c28da0baf04c2 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/boolean_content_preview.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useMemo } from 'react'; +// import { EuiDataGridColumn } from '@elastic/eui'; +import { OrdinalChartData } from './field_histograms'; +// import { ColumnChart } from '../../../../../../components/data_grid/column_chart'; // TODO copy component +import { FieldDataRowProps } from '../../types'; +import { getTFPercentage } from '../../utils'; + +export const BooleanContentPreview: FC = ({ config }) => { + const chartData = useMemo(() => { + const results = getTFPercentage(config); + if (results) { + const data = [ + { key: 'true', key_as_string: 'true', doc_count: results.trueCount }, + { key: 'false', key_as_string: 'false', doc_count: results.falseCount }, + ]; + return { id: config.fieldName, cardinality: 2, data, type: 'boolean' } as OrdinalChartData; + } + }, [config]); + if (!chartData || config.fieldName === undefined) return null; + + // const columnType: EuiDataGridColumn = { + // id: config.fieldName, + // schema: undefined, + // }; + // const dataTestSubj = `mlDataGridChart-${config.fieldName}`; + + return ( + <> + // + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/column_chart.scss b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/column_chart.scss new file mode 100644 index 000000000000000..63603ee9bd2ecf7 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/column_chart.scss @@ -0,0 +1,32 @@ +.dataGridChart__histogram { + width: 100%; + height: $euiSizeXL + $euiSizeXXL; +} + +.dataGridChart__legend { + @include euiTextTruncate; + @include euiFontSizeXS; + + color: $euiColorMediumShade; + display: block; + overflow-x: hidden; + margin: $euiSizeXS 0 0 0; + font-style: italic; + font-weight: normal; + text-align: left; +} + +.dataGridChart__legend--numeric { + text-align: right; +} + +.dataGridChart__legendBoolean { + width: 100%; + min-width: $euiButtonMinWidth; + td { text-align: center } +} + +/* Override to align column header to bottom of cell when no chart is available */ +.dataGrid .euiDataGridHeaderCell__content { + margin-top: auto; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/column_chart.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/column_chart.tsx new file mode 100644 index 000000000000000..ed4b82005db29da --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/column_chart.tsx @@ -0,0 +1,84 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import classNames from 'classnames'; + +import { BarSeries, Chart, Settings } from '@elastic/charts'; +import { EuiDataGridColumn } from '@elastic/eui'; + +import './column_chart.scss'; + +import { isUnsupportedChartData, ChartData } from './field_histograms'; + +import { useColumnChart } from './use_column_chart'; + +interface Props { + chartData: ChartData; + columnType: EuiDataGridColumn; + dataTestSubj: string; + hideLabel?: boolean; + maxChartColumns?: number; +} + +const columnChartTheme = { + background: { color: 'transparent' }, + chartMargins: { + left: 0, + right: 0, + top: 0, + bottom: 1, + }, + chartPaddings: { + left: 0, + right: 0, + top: 0, + bottom: 0, + }, + scales: { barsPadding: 0.1 }, +}; +export const ColumnChart: FC = ({ + chartData, + columnType, + dataTestSubj, + hideLabel, + maxChartColumns, +}) => { + const { data, legendText, xScaleType } = useColumnChart(chartData, columnType, maxChartColumns); + + return ( +
+ {!isUnsupportedChartData(chartData) && data.length > 0 && ( +
+ + + d.datum.color} + data={data} + /> + +
+ )} +
+ {legendText} +
+ {!hideLabel &&
{columnType.id}
} +
+ ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/distinct_values.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/distinct_values.tsx new file mode 100644 index 000000000000000..92e0d1a16229fac --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/distinct_values.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; + +import React from 'react'; + +export const DistinctValues = ({ cardinality }: { cardinality?: number }) => { + if (cardinality === undefined) return null; + return ( + + + + + + {cardinality} + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/document_stats.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/document_stats.tsx new file mode 100644 index 000000000000000..7d0bda6ac47ea73 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/document_stats.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; + +import React from 'react'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { roundToDecimalPlace } from '../../../utils'; + +export const DocumentStat = ({ config }: FieldDataRowProps) => { + const { stats } = config; + if (stats === undefined) return null; + + const { count, sampleCount } = stats; + if (count === undefined || sampleCount === undefined) return null; + + const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); + + return ( + + + + + + {count} ({docsPercent}%) + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/field_histograms.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/field_histograms.ts new file mode 100644 index 000000000000000..22b0195a579acdc --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/field_histograms.ts @@ -0,0 +1,68 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface NumericDataItem { + key: number; + key_as_string?: string | number; + doc_count: number; +} + +export interface NumericChartData { + data: NumericDataItem[]; + id: string; + interval: number; + stats: [number, number]; + type: 'numeric'; +} + +export const isNumericChartData = (arg: any): arg is NumericChartData => { + return ( + typeof arg === 'object' && + arg.hasOwnProperty('data') && + arg.hasOwnProperty('id') && + arg.hasOwnProperty('interval') && + arg.hasOwnProperty('stats') && + arg.hasOwnProperty('type') && + arg.type === 'numeric' + ); +}; + +export interface OrdinalDataItem { + key: string; + key_as_string?: string; + doc_count: number; +} + +export interface OrdinalChartData { + cardinality: number; + data: OrdinalDataItem[]; + id: string; + type: 'ordinal' | 'boolean'; +} + +export const isOrdinalChartData = (arg: any): arg is OrdinalChartData => { + return ( + typeof arg === 'object' && + arg.hasOwnProperty('data') && + arg.hasOwnProperty('cardinality') && + arg.hasOwnProperty('id') && + arg.hasOwnProperty('type') && + (arg.type === 'ordinal' || arg.type === 'boolean') + ); +}; + +export interface UnsupportedChartData { + id: string; + type: 'unsupported'; +} + +export const isUnsupportedChartData = (arg: any): arg is UnsupportedChartData => { + return typeof arg === 'object' && arg.hasOwnProperty('type') && arg.type === 'unsupported'; +}; + +export type ChartDataItem = NumericDataItem | OrdinalDataItem; +export type ChartData = NumericChartData | OrdinalChartData | UnsupportedChartData; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/index.ts new file mode 100644 index 000000000000000..e4c0cc80eeb3506 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { BooleanContentPreview } from './boolean_content_preview'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/number_content_preview.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/number_content_preview.tsx new file mode 100644 index 000000000000000..00150bdfe8b7a5e --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/number_content_preview.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useEffect, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import classNames from 'classnames'; +import { + MetricDistributionChart, + MetricDistributionChartData, + buildChartDataFromStats, +} from '../metric_distribution_chart'; +import { FieldVisConfig } from '../../types'; +import { kibanaFieldFormat, formatSingleValue } from '../../../utils'; + +const METRIC_DISTRIBUTION_CHART_WIDTH = 150; +const METRIC_DISTRIBUTION_CHART_HEIGHT = 80; + +export interface NumberContentPreviewProps { + config: FieldVisConfig; +} + +export const IndexBasedNumberContentPreview: FC = ({ config }) => { + const { stats, fieldFormat, fieldName } = config; + const defaultChartData: MetricDistributionChartData[] = []; + const [distributionChartData, setDistributionChartData] = useState(defaultChartData); + const [legendText, setLegendText] = useState<{ min: number; max: number } | undefined>(); + const dataTestSubj = `mlDataGridChart-${fieldName}`; + useEffect(() => { + const chartData = buildChartDataFromStats(stats, METRIC_DISTRIBUTION_CHART_WIDTH); + if ( + Array.isArray(chartData) && + chartData[0].x !== undefined && + chartData[chartData.length - 1].x !== undefined + ) { + setDistributionChartData(chartData); + setLegendText({ + min: formatSingleValue(chartData[0].x), + max: formatSingleValue(chartData[chartData.length - 1].x), + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
+
+ +
+
+ {legendText && ( + <> + + + + {kibanaFieldFormat(legendText.min, fieldFormat)} + + + {kibanaFieldFormat(legendText.max, fieldFormat)} + + + + )} +
+
+ ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/top_values_preview.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/top_values_preview.tsx new file mode 100644 index 000000000000000..63b15fdf30b3be7 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/top_values_preview.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { EuiDataGridColumn } from '@elastic/eui'; +import { ChartData, OrdinalDataItem } from './field_histograms'; +import { ColumnChart } from './column_chart'; +import type { FieldDataRowProps } from '../../types/field_data_row'; + +export const TopValuesPreview: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined) return null; + const { topValues, cardinality } = stats; + if (cardinality === undefined || topValues === undefined || config.fieldName === undefined) + return null; + + const data: OrdinalDataItem[] = topValues.map((d) => ({ + ...d, + key: d.key.toString(), + })); + const chartData: ChartData = { + cardinality, + data, + id: config.fieldName, + type: 'ordinal', + }; + const columnType: EuiDataGridColumn = { + id: config.fieldName, + schema: undefined, + }; + return ( + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/use_column_chart.test.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/use_column_chart.test.tsx new file mode 100644 index 000000000000000..2c92c366b2d731a --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/use_column_chart.test.tsx @@ -0,0 +1,177 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; +import '@testing-library/jest-dom/extend-expect'; + +import { KBN_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/public'; + +import { + isNumericChartData, + isOrdinalChartData, + isUnsupportedChartData, + NumericChartData, + OrdinalChartData, + UnsupportedChartData, +} from './field_histograms'; + +import { getFieldType, getLegendText, getXScaleType, useColumnChart } from './use_column_chart'; + +describe('getFieldType()', () => { + it('should return the Kibana field type for a given EUI data grid schema', () => { + expect(getFieldType('text')).toBe('string'); + expect(getFieldType('datetime')).toBe('date'); + expect(getFieldType('numeric')).toBe('number'); + expect(getFieldType('boolean')).toBe('boolean'); + expect(getFieldType('json')).toBe('object'); + expect(getFieldType('non-aggregatable')).toBe(undefined); + }); +}); + +describe('getXScaleType()', () => { + it('should return the corresponding x axis scale type for a Kibana field type', () => { + expect(getXScaleType(KBN_FIELD_TYPES.BOOLEAN)).toBe('ordinal'); + expect(getXScaleType(KBN_FIELD_TYPES.IP)).toBe('ordinal'); + expect(getXScaleType(KBN_FIELD_TYPES.STRING)).toBe('ordinal'); + expect(getXScaleType(KBN_FIELD_TYPES.DATE)).toBe('time'); + expect(getXScaleType(KBN_FIELD_TYPES.NUMBER)).toBe('linear'); + expect(getXScaleType(undefined)).toBe(undefined); + }); +}); + +const validNumericChartData: NumericChartData = { + data: [], + id: 'the-id', + interval: 10, + stats: [0, 0], + type: 'numeric', +}; + +const validOrdinalChartData: OrdinalChartData = { + cardinality: 10, + data: [], + id: 'the-id', + type: 'ordinal', +}; + +const validUnsupportedChartData: UnsupportedChartData = { id: 'the-id', type: 'unsupported' }; + +describe('isNumericChartData()', () => { + it('should return true for valid numeric chart data', () => { + expect(isNumericChartData(validNumericChartData)).toBe(true); + }); + it('should return false for invalid numeric chart data', () => { + expect(isNumericChartData(undefined)).toBe(false); + expect(isNumericChartData({})).toBe(false); + expect(isNumericChartData({ data: [] })).toBe(false); + expect(isNumericChartData(validOrdinalChartData)).toBe(false); + expect(isNumericChartData(validUnsupportedChartData)).toBe(false); + }); +}); + +describe('isOrdinalChartData()', () => { + it('should return true for valid ordinal chart data', () => { + expect(isOrdinalChartData(validOrdinalChartData)).toBe(true); + }); + it('should return false for invalid ordinal chart data', () => { + expect(isOrdinalChartData(undefined)).toBe(false); + expect(isOrdinalChartData({})).toBe(false); + expect(isOrdinalChartData({ data: [] })).toBe(false); + expect(isOrdinalChartData(validNumericChartData)).toBe(false); + expect(isOrdinalChartData(validUnsupportedChartData)).toBe(false); + }); +}); + +describe('isUnsupportedChartData()', () => { + it('should return true for unsupported chart data', () => { + expect(isUnsupportedChartData(validUnsupportedChartData)).toBe(true); + }); + it('should return false for invalid unsupported chart data', () => { + expect(isUnsupportedChartData(undefined)).toBe(false); + expect(isUnsupportedChartData({})).toBe(false); + expect(isUnsupportedChartData({ data: [] })).toBe(false); + expect(isUnsupportedChartData(validNumericChartData)).toBe(false); + expect(isUnsupportedChartData(validOrdinalChartData)).toBe(false); + }); +}); + +describe('getLegendText()', () => { + it('should return the chart legend text for unsupported chart types', () => { + expect(getLegendText(validUnsupportedChartData)).toBe('Chart not supported.'); + }); + it('should return the chart legend text for empty datasets', () => { + expect(getLegendText(validNumericChartData)).toBe('0 documents contain field.'); + }); + it('should return the chart legend text for boolean chart types', () => { + const { getByText } = render( + <> + {getLegendText({ + cardinality: 2, + data: [ + { key: 'true', key_as_string: 'true', doc_count: 10 }, + { key: 'false', key_as_string: 'false', doc_count: 20 }, + ], + id: 'the-id', + type: 'boolean', + })} + + ); + expect(getByText('true')).toBeInTheDocument(); + expect(getByText('false')).toBeInTheDocument(); + }); + it('should return the chart legend text for ordinal chart data with less than max categories', () => { + expect(getLegendText({ ...validOrdinalChartData, data: [{ key: 'cat', doc_count: 10 }] })).toBe( + '10 categories' + ); + }); + it('should return the chart legend text for ordinal chart data with more than max categories', () => { + expect( + getLegendText({ + ...validOrdinalChartData, + cardinality: 30, + data: [{ key: 'cat', doc_count: 10 }], + }) + ).toBe('top 20 of 30 categories'); + }); + it('should return the chart legend text for numeric datasets', () => { + expect( + getLegendText({ + ...validNumericChartData, + data: [{ key: 1, doc_count: 10 }], + stats: [1, 100], + }) + ).toBe('1 - 100'); + expect( + getLegendText({ + ...validNumericChartData, + data: [{ key: 1, doc_count: 10 }], + stats: [100, 100], + }) + ).toBe('100'); + expect( + getLegendText({ + ...validNumericChartData, + data: [{ key: 1, doc_count: 10 }], + stats: [1.2345, 6.3456], + }) + ).toBe('1.23 - 6.35'); + }); +}); + +describe('useColumnChart()', () => { + it('should return the column chart hook data', () => { + const { result } = renderHook(() => + useColumnChart(validNumericChartData, { id: 'the-id', schema: 'numeric' }) + ); + + expect(result.current.data).toStrictEqual([]); + expect(result.current.legendText).toBe('0 documents contain field.'); + expect(result.current.xScaleType).toBe('linear'); + }); +}); diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/use_column_chart.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/use_column_chart.tsx new file mode 100644 index 000000000000000..bd1df7f32c37514 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/field_data_row/use_column_chart.tsx @@ -0,0 +1,206 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { BehaviorSubject } from 'rxjs'; +import React from 'react'; + +import useObservable from 'react-use/lib/useObservable'; + +import { euiPaletteColorBlind, EuiDataGridColumn } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { KBN_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/public'; + +import { + isNumericChartData, + isOrdinalChartData, + ChartData, + ChartDataItem, + NumericDataItem, + OrdinalDataItem, +} from './field_histograms'; + +const NON_AGGREGATABLE = 'non-aggregatable'; + +export const hoveredRow$ = new BehaviorSubject(null); + +export const BAR_COLOR = euiPaletteColorBlind()[0]; +const BAR_COLOR_BLUR = euiPaletteColorBlind({ rotations: 2 })[10]; +const MAX_CHART_COLUMNS = 20; + +type XScaleType = 'ordinal' | 'time' | 'linear' | undefined; +export const getXScaleType = (kbnFieldType: KBN_FIELD_TYPES | undefined): XScaleType => { + switch (kbnFieldType) { + case KBN_FIELD_TYPES.BOOLEAN: + case KBN_FIELD_TYPES.IP: + case KBN_FIELD_TYPES.STRING: + return 'ordinal'; + case KBN_FIELD_TYPES.DATE: + return 'time'; + case KBN_FIELD_TYPES.NUMBER: + return 'linear'; + } +}; + +export const getFieldType = (schema: EuiDataGridColumn['schema']): KBN_FIELD_TYPES | undefined => { + if (schema === NON_AGGREGATABLE) { + return undefined; + } + + let fieldType: KBN_FIELD_TYPES; + + switch (schema) { + case 'datetime': + fieldType = KBN_FIELD_TYPES.DATE; + break; + case 'numeric': + fieldType = KBN_FIELD_TYPES.NUMBER; + break; + case 'boolean': + fieldType = KBN_FIELD_TYPES.BOOLEAN; + break; + case 'json': + fieldType = KBN_FIELD_TYPES.OBJECT; + break; + default: + fieldType = KBN_FIELD_TYPES.STRING; + } + + return fieldType; +}; + +type LegendText = string | JSX.Element; +export const getLegendText = ( + chartData: ChartData, + maxChartColumns = MAX_CHART_COLUMNS +): LegendText => { + if (chartData.type === 'unsupported') { + return i18n.translate('xpack.fileDataVisualizer.dataGridChart.histogramNotAvailable', { + defaultMessage: 'Chart not supported.', + }); + } + + if (chartData.data.length === 0) { + return i18n.translate('xpack.fileDataVisualizer.dataGridChart.notEnoughData', { + defaultMessage: `0 documents contain field.`, + }); + } + + if (chartData.type === 'boolean') { + return ( + + + + {chartData.data[0] !== undefined && } + {chartData.data[1] !== undefined && } + + +
{chartData.data[0].key_as_string}{chartData.data[1].key_as_string}
+ ); + } + + if (isOrdinalChartData(chartData) && chartData.cardinality <= maxChartColumns) { + return i18n.translate('xpack.fileDataVisualizer.dataGridChart.singleCategoryLegend', { + defaultMessage: `{cardinality, plural, one {# category} other {# categories}}`, + values: { cardinality: chartData.cardinality }, + }); + } + + if (isOrdinalChartData(chartData) && chartData.cardinality > maxChartColumns) { + return i18n.translate('xpack.fileDataVisualizer.dataGridChart.topCategoriesLegend', { + defaultMessage: `top {maxChartColumns} of {cardinality} categories`, + values: { cardinality: chartData.cardinality, maxChartColumns }, + }); + } + + if (isNumericChartData(chartData)) { + const fromValue = Math.round(chartData.stats[0] * 100) / 100; + const toValue = Math.round(chartData.stats[1] * 100) / 100; + + return fromValue !== toValue ? `${fromValue} - ${toValue}` : '' + fromValue; + } + + return ''; +}; + +interface ColumnChart { + data: ChartDataItem[]; + legendText: LegendText; + xScaleType: XScaleType; +} + +export const useColumnChart = ( + chartData: ChartData, + columnType: EuiDataGridColumn, + maxChartColumns?: number +): ColumnChart => { + const fieldType = getFieldType(columnType.schema); + + const hoveredRow = useObservable(hoveredRow$); + + const xScaleType = getXScaleType(fieldType); + + const getColor = (d: ChartDataItem) => { + if (hoveredRow === undefined || hoveredRow === null) { + return BAR_COLOR; + } + + if ( + isOrdinalChartData(chartData) && + xScaleType === 'ordinal' && + hoveredRow._source[columnType.id] === d.key + ) { + return BAR_COLOR; + } + + if ( + isNumericChartData(chartData) && + xScaleType === 'linear' && + hoveredRow._source[columnType.id] >= +d.key && + hoveredRow._source[columnType.id] < +d.key + chartData.interval + ) { + return BAR_COLOR; + } + + if ( + isNumericChartData(chartData) && + xScaleType === 'time' && + moment(hoveredRow._source[columnType.id]).unix() * 1000 >= +d.key && + moment(hoveredRow._source[columnType.id]).unix() * 1000 < +d.key + chartData.interval + ) { + return BAR_COLOR; + } + + return BAR_COLOR_BLUR; + }; + + let data: ChartDataItem[] = []; + + // The if/else if/else is a work-around because `.map()` doesn't work with union types. + // See TS Caveats for details: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-3.html#caveats + if (isOrdinalChartData(chartData)) { + data = chartData.data.map((d: OrdinalDataItem) => ({ + ...d, + key_as_string: d.key_as_string ?? d.key, + color: getColor(d), + })); + } else if (isNumericChartData(chartData)) { + data = chartData.data.map((d: NumericDataItem) => ({ + ...d, + key_as_string: d.key_as_string || d.key, + color: getColor(d), + })); + } + + return { + data, + legendText: getLegendText(chartData, maxChartColumns), + xScaleType, + }; +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/index.ts new file mode 100644 index 000000000000000..72947f2953cb832 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { MetricDistributionChart, MetricDistributionChartData } from './metric_distribution_chart'; +export { buildChartDataFromStats } from './metric_distribution_chart_data_builder'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart.tsx new file mode 100644 index 000000000000000..caa560488d499b3 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart.tsx @@ -0,0 +1,108 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { + AreaSeries, + Axis, + Chart, + CurveType, + Position, + ScaleType, + Settings, + TooltipValue, + TooltipValueFormatter, +} from '@elastic/charts'; + +import { MetricDistributionChartTooltipHeader } from './metric_distribution_chart_tooltip_header'; +import { kibanaFieldFormat } from '../../../utils'; +import { useDataVizChartTheme } from '../../hooks'; + +interface ChartTooltipValue extends TooltipValue { + skipHeader?: boolean; +} + +export interface MetricDistributionChartData { + x: number; + y: number; + dataMin: number; + dataMax: number; + percent: number; +} + +interface Props { + width: number; + height: number; + chartData: MetricDistributionChartData[]; + fieldFormat?: any; // Kibana formatter for field being viewed + hideXAxis?: boolean; +} + +const SPEC_ID = 'metric_distribution'; + +export const MetricDistributionChart: FC = ({ + width, + height, + chartData, + fieldFormat, + hideXAxis, +}) => { + // This value is shown to label the y axis values in the tooltip. + // Ideally we wouldn't show these values at all in the tooltip, + // but this is not yet possible with Elastic charts. + const seriesName = i18n.translate( + 'xpack.fileDataVisualizer.fieldDataCard.metricDistributionChart.seriesName', + { + defaultMessage: 'distribution', + } + ); + + const theme = useDataVizChartTheme(); + + const headerFormatter: TooltipValueFormatter = (tooltipData: ChartTooltipValue) => { + const xValue = tooltipData.value; + const chartPoint: MetricDistributionChartData | undefined = chartData.find( + (data) => data.x === xValue + ); + + return ( + + ); + }; + + return ( +
+ + + kibanaFieldFormat(d, fieldFormat)} + hide={hideXAxis === true} + /> + d.toFixed(3)} hide={true} /> + 0 ? chartData : [{ x: 0, y: 0 }]} + curve={CurveType.CURVE_STEP_AFTER} + /> + +
+ ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart_data_builder.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart_data_builder.tsx new file mode 100644 index 000000000000000..a65b6bdc7458fe8 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart_data_builder.tsx @@ -0,0 +1,156 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const METRIC_DISTRIBUTION_CHART_MIN_BAR_WIDTH = 3; // Minimum bar width, in pixels. +const METRIC_DISTRIBUTION_CHART_MAX_BAR_HEIGHT_FACTOR = 20; // Max bar height relative to median bar height. + +import { MetricDistributionChartData } from './metric_distribution_chart'; + +interface DistributionPercentile { + minValue: number; + maxValue: number; + percent: number; +} + +interface DistributionChartBar { + x0: number; + x1: number; + y: number; + dataMin: number; + dataMax: number; + percent: number; + isMinWidth: boolean; +} + +export function buildChartDataFromStats( + stats: any, + chartWidth: number +): MetricDistributionChartData[] { + // Process the raw percentiles data so it is in a suitable format for plotting in the metric distribution chart. + let chartData: MetricDistributionChartData[] = []; + + const distribution = stats.distribution; + if (distribution === undefined) { + return chartData; + } + + const percentiles: DistributionPercentile[] = distribution.percentiles; + if (percentiles.length === 0) { + return chartData; + } + + // Adjust x axis min and max if there is a single bar. + const minX = percentiles[0].minValue; + const maxX = percentiles[percentiles.length - 1].maxValue; + + let xAxisMin: number = minX; + let xAxisMax: number = maxX; + if (maxX === minX) { + if (minX !== 0) { + xAxisMin = 0; + xAxisMax = 2 * minX; + } else { + xAxisMax = 1; + } + } + + // Adjust the right hand x coordinates so that each bar is at least METRIC_DISTRIBUTION_CHART_MIN_BAR_WIDTH. + const minBarWidth = + (METRIC_DISTRIBUTION_CHART_MIN_BAR_WIDTH / chartWidth) * (xAxisMax - xAxisMin); + const processedData: DistributionChartBar[] = []; + let lastBar: DistributionChartBar; + percentiles.forEach((data, index) => { + if (index === 0) { + const bar: DistributionChartBar = { + x0: data.minValue, + x1: Math.max(data.minValue + minBarWidth, data.maxValue), + y: 0, // Set below + dataMin: data.minValue, + dataMax: data.maxValue, + percent: data.percent, + isMinWidth: false, + }; + + // Scale the height of the bar according to the range of data values in the bar. + bar.y = + (data.percent / (bar.x1 - bar.x0)) * + Math.max(1, minBarWidth / Math.max(data.maxValue - data.minValue, 0.5 * minBarWidth)); + bar.isMinWidth = data.maxValue <= data.minValue + minBarWidth; + processedData.push(bar); + lastBar = bar; + } else { + if (lastBar.isMinWidth === false || data.maxValue > lastBar.x1) { + const bar = { + x0: lastBar.x1, + x1: Math.max(lastBar.x1 + minBarWidth, data.maxValue), + y: 0, // Set below + dataMin: data.minValue, + dataMax: data.maxValue, + percent: data.percent, + isMinWidth: false, + }; + + // Scale the height of the bar according to the range of data values in the bar. + bar.y = + (data.percent / (bar.x1 - bar.x0)) * + Math.max(1, minBarWidth / Math.max(data.maxValue - data.minValue, 0.5 * minBarWidth)); + bar.isMinWidth = data.maxValue <= lastBar.x1 + minBarWidth; + processedData.push(bar); + lastBar = bar; + } else { + // Combine bars which are less than minBarWidth apart. + lastBar.percent = lastBar.percent + data.percent; + lastBar.y = lastBar.percent / (lastBar.x1 - lastBar.x0); + lastBar.dataMax = data.maxValue; + } + } + }); + + if (maxX !== minX) { + xAxisMax = processedData[processedData.length - 1].x1; + } + + // Adjust the maximum bar height to be (METRIC_DISTRIBUTION_CHART_MAX_BAR_HEIGHT_FACTOR * median bar height). + let barHeights = processedData.map((data) => data.y); + barHeights = barHeights.sort((a, b) => a - b); + + let maxBarHeight = 0; + const processedDataLength = processedData.length; + if (Math.abs(processedDataLength % 2) === 1) { + maxBarHeight = + METRIC_DISTRIBUTION_CHART_MAX_BAR_HEIGHT_FACTOR * + barHeights[Math.floor(processedDataLength / 2)]; + } else { + maxBarHeight = + (METRIC_DISTRIBUTION_CHART_MAX_BAR_HEIGHT_FACTOR * + (barHeights[Math.floor(processedDataLength / 2) - 1] + + barHeights[Math.floor(processedDataLength / 2)])) / + 2; + } + + processedData.forEach((data) => { + data.y = Math.min(data.y, maxBarHeight); + }); + + // Convert the data to the format used by the chart. + chartData = processedData.map((data) => { + const { x0, y, dataMin, dataMax, percent } = data; + return { x: x0, y, dataMin, dataMax, percent }; + }); + + // Add a final point to drop the curve back to the y axis. + const last = processedData[processedData.length - 1]; + chartData.push({ + x: last.x1, + y: 0, + dataMin: last.dataMin, + dataMax: last.dataMax, + percent: last.percent, + }); + + return chartData; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart_tooltip_header.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart_tooltip_header.tsx new file mode 100644 index 000000000000000..9fd613ac96b8e3c --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/components/metric_distribution_chart/metric_distribution_chart_tooltip_header.tsx @@ -0,0 +1,54 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import { MetricDistributionChartData } from './metric_distribution_chart'; +import { kibanaFieldFormat } from '../../../utils'; + +interface Props { + chartPoint: MetricDistributionChartData | undefined; + maxWidth: number; + fieldFormat?: any; // Kibana formatter for field being viewed +} + +export const MetricDistributionChartTooltipHeader: FC = ({ + chartPoint, + maxWidth, + fieldFormat, +}) => { + if (chartPoint === undefined) { + return null; + } + + return ( +
+ {chartPoint.dataMax > chartPoint.dataMin ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/data_visualizer_stats_table.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/data_visualizer_stats_table.tsx new file mode 100644 index 000000000000000..bfa40c487a2ac42 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/data_visualizer_stats_table.tsx @@ -0,0 +1,284 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo, useState } from 'react'; + +import { + CENTER_ALIGNMENT, + EuiBasicTableColumn, + EuiButtonIcon, + EuiFlexItem, + EuiIcon, + EuiInMemoryTable, + EuiText, + HorizontalAlignment, + LEFT_ALIGNMENT, + RIGHT_ALIGNMENT, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiTableComputedColumnType } from '@elastic/eui/src/components/basic_table/table_types'; +import { JOB_FIELD_TYPES, JobFieldType, DataVisualizerTableState } from '../../../../common'; +import { FieldTypeIcon } from '../field_type_icon'; +import { DocumentStat } from './components/field_data_row/document_stats'; +import { DistinctValues } from './components/field_data_row/distinct_values'; +import { IndexBasedNumberContentPreview } from './components/field_data_row/number_content_preview'; + +import { useTableSettings } from './use_table_settings'; +import { TopValuesPreview } from './components/field_data_row/top_values_preview'; +import { + FieldVisConfig, + FileBasedFieldVisConfig, + isIndexBasedFieldVisConfig, +} from './types/field_vis_config'; +import { FileBasedNumberContentPreview } from '../field_data_row'; +import { BooleanContentPreview } from './components/field_data_row'; + +const FIELD_NAME = 'fieldName'; + +export type ItemIdToExpandedRowMap = Record; + +type DataVisualizerTableItem = FieldVisConfig | FileBasedFieldVisConfig; +interface DataVisualizerTableProps { + items: T[]; + pageState: DataVisualizerTableState; + updatePageState: (update: DataVisualizerTableState) => void; + getItemIdToExpandedRowMap: (itemIds: string[], items: T[]) => ItemIdToExpandedRowMap; + extendedColumns?: Array>; +} + +export const DataVisualizerTable = ({ + items, + pageState, + updatePageState, + getItemIdToExpandedRowMap, + extendedColumns, +}: DataVisualizerTableProps) => { + const [expandedRowItemIds, setExpandedRowItemIds] = useState([]); + const [expandAll, toggleExpandAll] = useState(false); + + const { onTableChange, pagination, sorting } = useTableSettings( + items, + pageState, + updatePageState + ); + const showDistributions: boolean = + ('showDistributions' in pageState && pageState.showDistributions) ?? true; + const toggleShowDistribution = () => { + updatePageState({ + ...pageState, + showDistributions: !showDistributions, + }); + }; + + function toggleDetails(item: DataVisualizerTableItem) { + if (item.fieldName === undefined) return; + const index = expandedRowItemIds.indexOf(item.fieldName); + if (index !== -1) { + expandedRowItemIds.splice(index, 1); + } else { + expandedRowItemIds.push(item.fieldName); + } + + // spread to a new array otherwise the component wouldn't re-render + setExpandedRowItemIds([...expandedRowItemIds]); + } + + const columns = useMemo(() => { + const expanderColumn: EuiTableComputedColumnType = { + name: ( + toggleExpandAll(!expandAll)} + aria-label={ + !expandAll + ? i18n.translate( + 'xpack.fileDataVisualizer.datavisualizer.dataGrid.expandDetailsForAllAriaLabel', + { + defaultMessage: 'Expand details for all fields', + } + ) + : i18n.translate( + 'xpack.fileDataVisualizer.datavisualizer.dataGrid.collapseDetailsForAllAriaLabel', + { + defaultMessage: 'Collapse details for all fields', + } + ) + } + iconType={expandAll ? 'arrowUp' : 'arrowDown'} + /> + ), + align: RIGHT_ALIGNMENT, + width: '40px', + isExpander: true, + render: (item: DataVisualizerTableItem) => { + if (item.fieldName === undefined) return null; + const direction = expandedRowItemIds.includes(item.fieldName) ? 'arrowUp' : 'arrowDown'; + return ( + toggleDetails(item)} + aria-label={ + expandedRowItemIds.includes(item.fieldName) + ? i18n.translate('xpack.fileDataVisualizer.datavisualizer.dataGrid.rowCollapse', { + defaultMessage: 'Hide details for {fieldName}', + values: { fieldName: item.fieldName }, + }) + : i18n.translate('xpack.fileDataVisualizer.datavisualizer.dataGrid.rowExpand', { + defaultMessage: 'Show details for {fieldName}', + values: { fieldName: item.fieldName }, + }) + } + iconType={direction} + /> + ); + }, + 'data-test-subj': 'mlDataVisualizerTableColumnDetailsToggle', + }; + + const baseColumns = [ + expanderColumn, + { + field: 'type', + name: i18n.translate('xpack.fileDataVisualizer.datavisualizer.dataGrid.typeColumnName', { + defaultMessage: 'Type', + }), + render: (fieldType: JobFieldType) => { + return ; + }, + width: '75px', + sortable: true, + align: CENTER_ALIGNMENT as HorizontalAlignment, + 'data-test-subj': 'mlDataVisualizerTableColumnType', + }, + { + field: 'fieldName', + name: i18n.translate('xpack.fileDataVisualizer.datavisualizer.dataGrid.nameColumnName', { + defaultMessage: 'Name', + }), + sortable: true, + truncateText: true, + render: (fieldName: string) => ( + + {fieldName} + + ), + align: LEFT_ALIGNMENT as HorizontalAlignment, + 'data-test-subj': 'mlDataVisualizerTableColumnName', + }, + { + field: 'docCount', + name: i18n.translate( + 'xpack.fileDataVisualizer.datavisualizer.dataGrid.documentsCountColumnName', + { + defaultMessage: 'Documents (%)', + } + ), + render: (value: number | undefined, item: DataVisualizerTableItem) => ( + + ), + sortable: (item: DataVisualizerTableItem) => item?.stats?.count, + align: LEFT_ALIGNMENT as HorizontalAlignment, + 'data-test-subj': 'mlDataVisualizerTableColumnDocumentsCount', + }, + { + field: 'stats.cardinality', + name: i18n.translate( + 'xpack.fileDataVisualizer.datavisualizer.dataGrid.distinctValuesColumnName', + { + defaultMessage: 'Distinct values', + } + ), + render: (cardinality?: number) => , + sortable: true, + align: LEFT_ALIGNMENT as HorizontalAlignment, + 'data-test-subj': 'mlDataVisualizerTableColumnDistinctValues', + }, + { + name: ( +
+ + {i18n.translate( + 'xpack.fileDataVisualizer.datavisualizer.dataGrid.distributionsColumnName', + { + defaultMessage: 'Distributions', + } + )} + toggleShowDistribution()} + aria-label={i18n.translate( + 'xpack.fileDataVisualizer.datavisualizer.dataGrid.showDistributionsAriaLabel', + { + defaultMessage: 'Show distributions', + } + )} + /> +
+ ), + render: (item: DataVisualizerTableItem) => { + if (item === undefined || showDistributions === false) return null; + if ( + (item.type === JOB_FIELD_TYPES.KEYWORD || item.type === JOB_FIELD_TYPES.IP) && + item.stats?.topValues !== undefined + ) { + return ; + } + + if (item.type === JOB_FIELD_TYPES.NUMBER) { + if (isIndexBasedFieldVisConfig(item) && item.stats?.distribution !== undefined) { + return ; + } else { + return ; + } + } + + if (item.type === JOB_FIELD_TYPES.BOOLEAN) { + return ; + } + + return null; + }, + align: LEFT_ALIGNMENT as HorizontalAlignment, + 'data-test-subj': 'mlDataVisualizerTableColumnDistribution', + }, + ]; + return extendedColumns ? [...baseColumns, ...extendedColumns] : baseColumns; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [expandAll, showDistributions, updatePageState, extendedColumns]); + + const itemIdToExpandedRowMap = useMemo(() => { + let itemIds = expandedRowItemIds; + if (expandAll) { + itemIds = items.map((i) => i[FIELD_NAME]).filter((f) => f !== undefined) as string[]; + } + return getItemIdToExpandedRowMap(itemIds, items); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [expandAll, items, expandedRowItemIds]); + + return ( + + + className={'dataVisualizer'} + items={items} + itemId={FIELD_NAME} + columns={columns} + pagination={pagination} + sorting={sorting} + isExpandable={true} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} + isSelectable={false} + onTableChange={onTableChange} + data-test-subj={'mlDataVisualizerTable'} + rowProps={(item) => ({ + 'data-test-subj': `mlDataVisualizerRow row-${item.fieldName}`, + })} + /> + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/color_range_legend.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/color_range_legend.tsx new file mode 100644 index 000000000000000..58be31a53e9c528 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/color_range_legend.tsx @@ -0,0 +1,146 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useRef, FC } from 'react'; +import d3 from 'd3'; + +import { EuiText } from '@elastic/eui'; + +const COLOR_RANGE_RESOLUTION = 10; + +interface ColorRangeLegendProps { + colorRange: (d: number) => string; + justifyTicks?: boolean; + showTicks?: boolean; + title?: string; + width?: number; +} + +/** + * Component to render a legend for color ranges to be used for color coding + * table cells and visualizations. + * + * This current version supports normalized value ranges (0-1) only. + * + * @param props ColorRangeLegendProps + */ +export const ColorRangeLegend: FC = ({ + colorRange, + justifyTicks = false, + showTicks = true, + title, + width = 250, +}) => { + const d3Container = useRef(null); + + const scale = d3.range(COLOR_RANGE_RESOLUTION + 1).map((d) => ({ + offset: (d / COLOR_RANGE_RESOLUTION) * 100, + stopColor: colorRange(d / COLOR_RANGE_RESOLUTION), + })); + + useEffect(() => { + if (d3Container.current === null) { + return; + } + + const wrapperHeight = 32; + const wrapperWidth = width; + + // top: 2 — adjust vertical alignment with title text + // bottom: 20 — room for axis ticks and labels + // left/right: 1 — room for first and last axis tick + // when justifyTicks is enabled, the left margin is increased to not cut off the first tick label + const margin = { top: 2, bottom: 20, left: justifyTicks || !showTicks ? 1 : 4, right: 1 }; + + const legendWidth = wrapperWidth - margin.left - margin.right; + const legendHeight = wrapperHeight - margin.top - margin.bottom; + + // remove, then redraw the legend + d3.select(d3Container.current).selectAll('*').remove(); + + const wrapper = d3 + .select(d3Container.current) + .classed('colorRangeLegend', true) + .attr('width', wrapperWidth) + .attr('height', wrapperHeight) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // append gradient bar + const gradient = wrapper + .append('defs') + .append('linearGradient') + .attr('id', 'colorRangeGradient') + .attr('x1', '0%') + .attr('y1', '0%') + .attr('x2', '100%') + .attr('y2', '0%') + .attr('spreadMethod', 'pad'); + + scale.forEach(function (d) { + gradient + .append('stop') + .attr('offset', `${d.offset}%`) + .attr('stop-color', d.stopColor) + .attr('stop-opacity', 1); + }); + + wrapper + .append('rect') + .attr('x1', 0) + .attr('y1', 0) + .attr('width', legendWidth) + .attr('height', legendHeight) + .style('fill', 'url(#colorRangeGradient)'); + + const axisScale = d3.scale.linear().domain([0, 1]).range([0, legendWidth]); + + // Using this formatter ensures we get e.g. `0` and not `0.0`, but still `0.1`, `0.2` etc. + const tickFormat = d3.format(''); + const legendAxis = d3.svg + .axis() + .scale(axisScale) + .orient('bottom') + .tickFormat(tickFormat) + .tickSize(legendHeight + 4) + .ticks(legendWidth / 40); + + wrapper + .append('g') + .attr('class', 'legend axis') + .attr('transform', 'translate(0, 0)') + .call(legendAxis); + + // Adjust the alignment of the first and last tick text + // so that the tick labels don't overflow the color range. + if (justifyTicks || !showTicks) { + const text = wrapper.selectAll('text')[0]; + if (text.length > 1) { + d3.select(text[0]).style('text-anchor', 'start'); + d3.select(text[text.length - 1]).style('text-anchor', 'end'); + } + } + + if (!showTicks) { + wrapper.selectAll('.axis line').style('display', 'none'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(scale), d3Container.current]); + + if (title === undefined) { + return ; + } + + return ( + <> + +

{title}

+
+ + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/index.ts new file mode 100644 index 000000000000000..85d85f51a623fcc --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { useDataVizChartTheme } from './use_data_viz_chart_theme'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_color_range.test.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_color_range.test.ts new file mode 100644 index 000000000000000..55888c607c28727 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_color_range.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { influencerColorScaleFactory } from './use_color_range'; + +describe('useColorRange', () => { + test('influencerColorScaleFactory(1)', () => { + const influencerColorScale = influencerColorScaleFactory(1); + + expect(influencerColorScale(0)).toBe(0); + expect(influencerColorScale(0.1)).toBe(0.1); + expect(influencerColorScale(0.2)).toBe(0.2); + expect(influencerColorScale(0.3)).toBe(0.3); + expect(influencerColorScale(0.4)).toBe(0.4); + expect(influencerColorScale(0.5)).toBe(0.5); + expect(influencerColorScale(0.6)).toBe(0.6); + expect(influencerColorScale(0.7)).toBe(0.7); + expect(influencerColorScale(0.8)).toBe(0.8); + expect(influencerColorScale(0.9)).toBe(0.9); + expect(influencerColorScale(1)).toBe(1); + }); + + test('influencerColorScaleFactory(2)', () => { + const influencerColorScale = influencerColorScaleFactory(2); + + expect(influencerColorScale(0)).toBe(0); + expect(influencerColorScale(0.1)).toBe(0); + expect(influencerColorScale(0.2)).toBe(0); + expect(influencerColorScale(0.3)).toBe(0); + expect(influencerColorScale(0.4)).toBe(0); + expect(influencerColorScale(0.5)).toBe(0); + expect(influencerColorScale(0.6)).toBe(0.04999999999999999); + expect(influencerColorScale(0.7)).toBe(0.09999999999999998); + expect(influencerColorScale(0.8)).toBe(0.15000000000000002); + expect(influencerColorScale(0.9)).toBe(0.2); + expect(influencerColorScale(1)).toBe(0.25); + }); + + test('influencerColorScaleFactory(3)', () => { + const influencerColorScale = influencerColorScaleFactory(3); + + expect(influencerColorScale(0)).toBe(0); + expect(influencerColorScale(0.1)).toBe(0); + expect(influencerColorScale(0.2)).toBe(0); + expect(influencerColorScale(0.3)).toBe(0); + expect(influencerColorScale(0.4)).toBe(0.05000000000000003); + expect(influencerColorScale(0.5)).toBe(0.125); + expect(influencerColorScale(0.6)).toBe(0.2); + expect(influencerColorScale(0.7)).toBe(0.27499999999999997); + expect(influencerColorScale(0.8)).toBe(0.35000000000000003); + expect(influencerColorScale(0.9)).toBe(0.425); + expect(influencerColorScale(1)).toBe(0.5); + }); +}); diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_color_range.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_color_range.ts new file mode 100644 index 000000000000000..e24134507e3a9f9 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_color_range.ts @@ -0,0 +1,219 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import d3 from 'd3'; +import { useMemo } from 'react'; +import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; +import euiThemeDark from '@elastic/eui/dist/eui_theme_dark.json'; + +import { i18n } from '@kbn/i18n'; + +import { useFileDataVisualizerKibana } from '../../../kibana_context'; + +/** + * Custom color scale factory that takes the amount of feature influencers + * into account to adjust the contrast of the color range. This is used for + * color coding for outlier detection where the amount of feature influencers + * affects the threshold from which the influencers value can actually be + * considered influential. + * + * @param n number of influencers + * @returns a function suitable as a preprocessor for d3.scale.linear() + */ +export const influencerColorScaleFactory = (n: number) => (t: number) => { + // for 1 influencer or less we fall back to a plain linear scale. + if (n <= 1) { + return t; + } + + if (t < 1 / n) { + return 0; + } + if (t < 3 / n) { + return (n / 4) * (t - 1 / n); + } + return 0.5 + (t - 3 / n); +}; + +export enum COLOR_RANGE_SCALE { + LINEAR = 'linear', + INFLUENCER = 'influencer', + SQRT = 'sqrt', +} + +/** + * Color range scale options in the format for EuiSelect's options prop. + */ +export const colorRangeScaleOptions = [ + { + value: COLOR_RANGE_SCALE.LINEAR, + text: i18n.translate('xpack.fileDataVisualizer.components.colorRangeLegend.linearScaleLabel', { + defaultMessage: 'Linear', + }), + }, + { + value: COLOR_RANGE_SCALE.INFLUENCER, + text: i18n.translate( + 'xpack.fileDataVisualizer.components.colorRangeLegend.influencerScaleLabel', + { + defaultMessage: 'Influencer custom scale', + } + ), + }, + { + value: COLOR_RANGE_SCALE.SQRT, + text: i18n.translate('xpack.fileDataVisualizer.components.colorRangeLegend.sqrtScaleLabel', { + defaultMessage: 'Sqrt', + }), + }, +]; + +export enum COLOR_RANGE { + BLUE = 'blue', + RED = 'red', + RED_GREEN = 'red-green', + GREEN_RED = 'green-red', + YELLOW_GREEN_BLUE = 'yellow-green-blue', +} + +/** + * Color range options in the format for EuiSelect's options prop. + */ +export const colorRangeOptions = [ + { + value: COLOR_RANGE.BLUE, + text: i18n.translate( + 'xpack.fileDataVisualizer.components.colorRangeLegend.blueColorRangeLabel', + { + defaultMessage: 'Blue', + } + ), + }, + { + value: COLOR_RANGE.RED, + text: i18n.translate( + 'xpack.fileDataVisualizer.components.colorRangeLegend.redColorRangeLabel', + { + defaultMessage: 'Red', + } + ), + }, + { + value: COLOR_RANGE.RED_GREEN, + text: i18n.translate( + 'xpack.fileDataVisualizer.components.colorRangeLegend.redGreenColorRangeLabel', + { + defaultMessage: 'Red - Green', + } + ), + }, + { + value: COLOR_RANGE.GREEN_RED, + text: i18n.translate( + 'xpack.fileDataVisualizer.components.colorRangeLegend.greenRedColorRangeLabel', + { + defaultMessage: 'Green - Red', + } + ), + }, + { + value: COLOR_RANGE.YELLOW_GREEN_BLUE, + text: i18n.translate( + 'xpack.fileDataVisualizer.components.colorRangeLegend.yellowGreenBlueColorRangeLabel', + { + defaultMessage: 'Yellow - Green - Blue', + } + ), + }, +]; + +/** + * A custom Yellow-Green-Blue color range to demonstrate the support + * for more complex ranges with more than two colors. + */ +const coloursYGB = [ + '#FFFFDD', + '#AAF191', + '#80D385', + '#61B385', + '#3E9583', + '#217681', + '#285285', + '#1F2D86', + '#000086', +]; +const colourRangeYGB = d3.range(0, 1, 1.0 / (coloursYGB.length - 1)); +colourRangeYGB.push(1); + +const colorDomains = { + [COLOR_RANGE.BLUE]: [0, 1], + [COLOR_RANGE.RED]: [0, 1], + [COLOR_RANGE.RED_GREEN]: [0, 1], + [COLOR_RANGE.GREEN_RED]: [0, 1], + [COLOR_RANGE.YELLOW_GREEN_BLUE]: colourRangeYGB, +}; + +/** + * Custom hook to get a d3 based color range to be used for color coding in table cells. + * + * @param colorRange COLOR_RANGE enum. + * @param colorRangeScale COLOR_RANGE_SCALE enum. + * @param featureCount + */ +export const useColorRange = ( + colorRange = COLOR_RANGE.BLUE, + colorRangeScale = COLOR_RANGE_SCALE.LINEAR, + featureCount = 1 +) => { + const { euiTheme } = useCurrentEuiTheme(); + + const colorRanges: Record = { + [COLOR_RANGE.BLUE]: [ + d3.rgb(euiTheme.euiColorEmptyShade).toString(), + d3.rgb(euiTheme.euiColorVis1).toString(), + ], + [COLOR_RANGE.RED]: [ + d3.rgb(euiTheme.euiColorEmptyShade).toString(), + d3.rgb(euiTheme.euiColorDanger).toString(), + ], + [COLOR_RANGE.RED_GREEN]: ['red', 'green'], + [COLOR_RANGE.GREEN_RED]: ['green', 'red'], + [COLOR_RANGE.YELLOW_GREEN_BLUE]: coloursYGB, + }; + + const linearScale = d3.scale + .linear() + .domain(colorDomains[colorRange]) + .range(colorRanges[colorRange]); + const influencerColorScale = influencerColorScaleFactory(featureCount); + const influencerScaleLinearWrapper = (n: number) => linearScale(influencerColorScale(n)); + + const scaleTypes = { + [COLOR_RANGE_SCALE.LINEAR]: linearScale, + [COLOR_RANGE_SCALE.INFLUENCER]: influencerScaleLinearWrapper, + [COLOR_RANGE_SCALE.SQRT]: d3.scale + .sqrt() + .domain(colorDomains[colorRange]) + // typings for .range() incorrectly don't allow passing in a color extent. + // @ts-ignore + .range(colorRanges[colorRange]), + }; + + return scaleTypes[colorRangeScale]; +}; + +export type EuiThemeType = typeof euiThemeLight | typeof euiThemeDark; + +export function useCurrentEuiTheme() { + const { + services: { uiSettings }, + } = useFileDataVisualizerKibana(); + return useMemo( + () => ({ euiTheme: uiSettings.get('theme:darkMode') ? euiThemeDark : euiThemeLight }), + [uiSettings] + ); +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_data_viz_chart_theme.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_data_viz_chart_theme.ts new file mode 100644 index 000000000000000..ad31ca2d0942018 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/hooks/use_data_viz_chart_theme.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PartialTheme } from '@elastic/charts'; +import { useMemo } from 'react'; +import { useCurrentEuiTheme } from './use_color_range'; +export const useDataVizChartTheme = (): PartialTheme => { + const { euiTheme } = useCurrentEuiTheme(); + const chartTheme = useMemo(() => { + const AREA_SERIES_COLOR = euiTheme.euiColorVis0; + return { + axes: { + tickLabel: { + fontSize: parseInt(euiTheme.euiFontSizeXS, 10), + fontFamily: euiTheme.euiFontFamily, + fontStyle: 'italic', + }, + }, + background: { color: 'transparent' }, + chartMargins: { + left: 0, + right: 0, + top: 0, + bottom: 0, + }, + chartPaddings: { + left: 0, + right: 0, + top: 4, + bottom: 0, + }, + scales: { barsPadding: 0.1 }, + colors: { + vizColors: [AREA_SERIES_COLOR], + }, + areaSeriesStyle: { + line: { + strokeWidth: 1, + visible: true, + }, + point: { + visible: false, + radius: 0, + opacity: 0, + }, + area: { visible: true, opacity: 1 }, + }, + }; + }, [euiTheme]); + return chartTheme; +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/index.ts new file mode 100644 index 000000000000000..3009470af485895 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { DataVisualizerTable, ItemIdToExpandedRowMap } from './data_visualizer_stats_table'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/field_data_row.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/field_data_row.ts new file mode 100644 index 000000000000000..24209af23ceb4a2 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/field_data_row.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FieldVisConfig, FileBasedFieldVisConfig } from './field_vis_config'; + +export interface FieldDataRowProps { + config: FieldVisConfig | FileBasedFieldVisConfig; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/field_vis_config.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/field_vis_config.ts new file mode 100644 index 000000000000000..e9ef0cd75e286af --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/field_vis_config.ts @@ -0,0 +1,98 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { JobFieldType } from '../../../../../common'; + +export interface Percentile { + percent: number; + minValue: number; + maxValue: number; +} + +export interface MetricFieldVisStats { + avg?: number; + distribution?: { + percentiles: Percentile[]; + maxPercentile: number; + minPercentile: 0; + }; + max?: number; + median?: number; + min?: number; +} + +interface DocumentCountBuckets { + [key: string]: number; +} + +export interface FieldVisStats { + cardinality?: number; + count?: number; + sampleCount?: number; + trueCount?: number; + falseCount?: number; + earliest?: number; + latest?: number; + documentCounts?: { + buckets?: DocumentCountBuckets; + }; + avg?: number; + distribution?: { + percentiles: Percentile[]; + maxPercentile: number; + minPercentile: 0; + }; + fieldName?: string; + isTopValuesSampled?: boolean; + max?: number; + median?: number; + min?: number; + topValues?: Array<{ key: number | string; doc_count: number }>; + topValuesSampleSize?: number; + topValuesSamplerShardSize?: number; + examples?: Array; + timeRangeEarliest?: number; + timeRangeLatest?: number; +} + +// The internal representation of the configuration used to build the visuals +// which display the field information. +export interface FieldVisConfig { + type: JobFieldType; + fieldName?: string; + existsInDocs: boolean; + aggregatable: boolean; + loading: boolean; + stats?: FieldVisStats; + fieldFormat?: any; + isUnsupportedType?: boolean; +} + +export interface FileBasedFieldVisConfig { + type: JobFieldType; + fieldName?: string; + stats?: FieldVisStats; + format?: string; +} + +export interface FileBasedUnknownFieldVisConfig { + fieldName: string; + type: 'text' | 'unknown'; + stats: { mean: number; count: number; sampleCount: number; cardinality: number }; +} + +export function isFileBasedFieldVisConfig( + field: FieldVisConfig | FileBasedFieldVisConfig +): field is FileBasedFieldVisConfig { + return !field.hasOwnProperty('existsInDocs'); +} + +export function isIndexBasedFieldVisConfig( + field: FieldVisConfig | FileBasedFieldVisConfig +): field is FieldVisConfig { + return field.hasOwnProperty('existsInDocs'); +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/index.ts new file mode 100644 index 000000000000000..161829461aa26de --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/types/index.ts @@ -0,0 +1,16 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { FieldDataRowProps } from './field_data_row'; +export { + FieldVisConfig, + FileBasedFieldVisConfig, + FieldVisStats, + MetricFieldVisStats, + isFileBasedFieldVisConfig, + isIndexBasedFieldVisConfig, +} from './field_vis_config'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/use_table_settings.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/use_table_settings.ts new file mode 100644 index 000000000000000..e2ff18a8001aaea --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/use_table_settings.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Direction, EuiBasicTableProps, Pagination, PropertySort } from '@elastic/eui'; +import { useCallback, useMemo } from 'react'; + +import { DataVisualizerTableState } from '../../../../common'; + +const PAGE_SIZE_OPTIONS = [10, 25, 50]; + +interface UseTableSettingsReturnValue { + onTableChange: EuiBasicTableProps['onChange']; + pagination: Pagination; + sorting: { sort: PropertySort }; +} + +export function useTableSettings( + items: TypeOfItem[], + pageState: DataVisualizerTableState, + updatePageState: (update: DataVisualizerTableState) => void +): UseTableSettingsReturnValue { + const { pageIndex, pageSize, sortField, sortDirection } = pageState; + + const onTableChange: EuiBasicTableProps['onChange'] = useCallback( + ({ page, sort }) => { + const result = { + ...pageState, + pageIndex: page?.index ?? pageState.pageIndex, + pageSize: page?.size ?? pageState.pageSize, + sortField: (sort?.field as string) ?? pageState.sortField, + sortDirection: sort?.direction ?? pageState.sortDirection, + }; + updatePageState(result); + }, + [pageState, updatePageState] + ); + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + totalItemCount: items.length, + pageSizeOptions: PAGE_SIZE_OPTIONS, + }), + [items, pageIndex, pageSize] + ); + + const sorting = useMemo( + () => ({ + sort: { + field: sortField as string, + direction: sortDirection as Direction, + }, + }), + [sortField, sortDirection] + ); + + return { onTableChange, pagination, sorting }; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/utils.ts b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/utils.ts new file mode 100644 index 000000000000000..27da91153b3baf1 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/stats_table/utils.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FileBasedFieldVisConfig } from './types'; + +export const getTFPercentage = (config: FileBasedFieldVisConfig) => { + const { stats } = config; + if (stats === undefined) return null; + const { count } = stats; + // use stats from index based config + let { trueCount, falseCount } = stats; + + // use stats from file based find structure results + if (stats.trueCount === undefined || stats.falseCount === undefined) { + if (config?.stats?.topValues) { + config.stats.topValues.forEach((doc) => { + if (doc.doc_count !== undefined) { + if (doc.key.toString().toLowerCase() === 'false') { + falseCount = doc.doc_count; + } + if (doc.key.toString().toLowerCase() === 'true') { + trueCount = doc.doc_count; + } + } + }); + } + } + if (count === undefined || trueCount === undefined || falseCount === undefined) return null; + return { + count, + trueCount, + falseCount, + }; +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/top_values/_top_values.scss b/x-pack/plugins/file_data_visualizer/public/application/components/top_values/_top_values.scss new file mode 100644 index 000000000000000..05fa1bfa94b2d7f --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/top_values/_top_values.scss @@ -0,0 +1,19 @@ +.fieldDataTopValuesContainer { + padding-top: $euiSizeXS; +} + +.topValuesValueLabelContainer { + margin-right: $euiSizeM; + &.topValuesValueLabelContainer--small { + width:70px; + } + + &.topValuesValueLabelContainer--large { + width: 200px; + } +} + +.topValuesPercentLabelContainer { + margin-left: $euiSizeM; + width:70px; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/top_values/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/top_values/index.ts new file mode 100644 index 000000000000000..c006b37fe2794c2 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/top_values/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { TopValues } from './top_values'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/top_values/top_values.tsx b/x-pack/plugins/file_data_visualizer/public/application/components/top_values/top_values.tsx new file mode 100644 index 000000000000000..c1815fad41de8ec --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/top_values/top_values.tsx @@ -0,0 +1,115 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, Fragment } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiProgress, + EuiSpacer, + EuiText, + EuiToolTip, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import classNames from 'classnames'; +import { roundToDecimalPlace, kibanaFieldFormat } from '../utils'; +import { ExpandedRowFieldHeader } from '../stats_table/components/expanded_row_field_header'; +import { FieldVisStats } from '../stats_table/types'; + +interface Props { + stats: FieldVisStats | undefined; + fieldFormat?: any; + barColor?: 'primary' | 'secondary' | 'danger' | 'subdued' | 'accent'; + compressed?: boolean; +} + +function getPercentLabel(docCount: number, topValuesSampleSize: number): string { + const percent = (100 * docCount) / topValuesSampleSize; + if (percent >= 0.1) { + return `${roundToDecimalPlace(percent, 1)}%`; + } else { + return '< 0.1%'; + } +} + +export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed }) => { + if (stats === undefined) return null; + const { + topValues, + topValuesSampleSize, + topValuesSamplerShardSize, + count, + isTopValuesSampled, + } = stats; + const progressBarMax = isTopValuesSampled === true ? topValuesSampleSize : count; + return ( + + + + + +
+ {Array.isArray(topValues) && + topValues.map((value) => ( + + + + + {kibanaFieldFormat(value.key, fieldFormat)} + + + + + + + {progressBarMax !== undefined && ( + + + {getPercentLabel(value.doc_count, progressBarMax)} + + + )} + + ))} + {isTopValuesSampled === true && ( + + + + + + + )} +
+
+ ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/utils/format_value.ts b/x-pack/plugins/file_data_visualizer/public/application/components/utils/format_value.ts new file mode 100644 index 000000000000000..5e12302a598ff66 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/utils/format_value.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * Formatter for 'typical' and 'actual' values from machine learning results. + * For detectors which use the time_of_week or time_of_day + * functions, the filter converts the raw number, which is the number of seconds since + * midnight, into a human-readable date/time format. + */ + +import moment from 'moment'; +const SIGFIGS_IF_ROUNDING = 3; // Number of sigfigs to use for values < 10 + +// Formats a single value according to the specified ML function. +// If a Kibana fieldFormat is not supplied, will fall back to default +// formatting depending on the magnitude of the value. +// For time_of_day or time_of_week functions the anomaly record +// containing the timestamp of the anomaly should be supplied in +// order to correctly format the day or week offset to the time of the anomaly. +export function formatSingleValue( + value: number, + func?: string, + fieldFormat?: any, + record?: any // TODO remove record, not needed for file upload +) { + if (value === undefined || value === null) { + return ''; + } + + // If the analysis function is time_of_week/day, format as day/time. + // For time_of_week / day, actual / typical is the UTC offset in seconds from the + // start of the week / day, so need to manipulate to UTC moment of the start of the week / day + // that the anomaly occurred using record timestamp if supplied, add on the offset, and finally + // revert back to configured timezone for formatting. + if (func === 'time_of_week') { + const d = + record !== undefined && record.timestamp !== undefined + ? new Date(record.timestamp) + : new Date(); + const utcMoment = moment.utc(d).startOf('week').add(value, 's'); + return moment(utcMoment.valueOf()).format('ddd HH:mm'); + } else if (func === 'time_of_day') { + const d = + record !== undefined && record.timestamp !== undefined + ? new Date(record.timestamp) + : new Date(); + const utcMoment = moment.utc(d).startOf('day').add(value, 's'); + return moment(utcMoment.valueOf()).format('HH:mm'); + } else { + if (fieldFormat !== undefined) { + return fieldFormat.convert(value, 'text'); + } else { + // If no Kibana FieldFormat object provided, + // format the value depending on its magnitude. + const absValue = Math.abs(value); + if (absValue >= 10000 || absValue === Math.floor(absValue)) { + // Output 0 decimal places if whole numbers or >= 10000 + if (fieldFormat !== undefined) { + return fieldFormat.convert(value, 'text'); + } else { + return Number(value.toFixed(0)); + } + } else if (absValue >= 10) { + // Output to 1 decimal place between 10 and 10000 + return Number(value.toFixed(1)); + } else { + // For values < 10, output to 3 significant figures + let multiple; + if (value > 0) { + multiple = Math.pow( + 10, + SIGFIGS_IF_ROUNDING - Math.floor(Math.log(value) / Math.LN10) - 1 + ); + } else { + multiple = Math.pow( + 10, + SIGFIGS_IF_ROUNDING - Math.floor(Math.log(-1 * value) / Math.LN10) - 1 + ); + } + return Math.round(value * multiple) / multiple; + } + } + } +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/utils/index.ts b/x-pack/plugins/file_data_visualizer/public/application/components/utils/index.ts new file mode 100644 index 000000000000000..b4c491eee8fd415 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/utils/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { createUrlOverrides, processResults, readFile, DEFAULT_LINES_TO_SAMPLE } from './utils'; +export { roundToDecimalPlace } from './round_to_decimal_place'; +export { kibanaFieldFormat } from './kibana_field_format'; +export { numberAsOrdinal } from './number_as_ordinal'; +export { formatSingleValue } from './format_value'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/utils/kibana_field_format.ts b/x-pack/plugins/file_data_visualizer/public/application/components/utils/kibana_field_format.ts new file mode 100644 index 000000000000000..0218b7d62655c2f --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/utils/kibana_field_format.ts @@ -0,0 +1,19 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * Formatter which uses the fieldFormat object of a Kibana index pattern + * field to format the value of a field. + */ + +export function kibanaFieldFormat(value: any, fieldFormat: any) { + if (fieldFormat !== undefined && fieldFormat !== null) { + return fieldFormat.convert(value, 'text'); + } else { + return value; + } +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/utils/number_as_ordinal.test.ts b/x-pack/plugins/file_data_visualizer/public/application/components/utils/number_as_ordinal.test.ts new file mode 100644 index 000000000000000..6990bf0923ac353 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/utils/number_as_ordinal.test.ts @@ -0,0 +1,29 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { numberAsOrdinal } from './number_as_ordinal'; + +describe('numberAsOrdinal formatter', () => { + const tests = [ + { number: 0, asOrdinal: '0th' }, + { number: 1, asOrdinal: '1st' }, + { number: 2.2, asOrdinal: '2nd' }, + { number: 3, asOrdinal: '3rd' }, + { number: 5, asOrdinal: '5th' }, + { number: 10, asOrdinal: '10th' }, + { number: 11, asOrdinal: '11th' }, + { number: 22, asOrdinal: '22nd' }, + { number: 33, asOrdinal: '33rd' }, + { number: 44.4, asOrdinal: '44th' }, + { number: 100, asOrdinal: '100th' }, + ]; + test('returns the expected numeral format', () => { + tests.forEach((test) => { + expect(numberAsOrdinal(test.number)).toBe(test.asOrdinal); + }); + }); +}); diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/utils/number_as_ordinal.ts b/x-pack/plugins/file_data_visualizer/public/application/components/utils/number_as_ordinal.ts new file mode 100644 index 000000000000000..3a2707cc47783c9 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/utils/number_as_ordinal.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// @ts-ignore +import numeral from '@elastic/numeral'; + +/** + * Formats the supplied number as ordinal e.g. 15 as 15th. + * Formatting first converts the supplied number to an integer by flooring. + * @param {number} value to format as an ordinal + * @return {string} number formatted as an ordinal e.g. 15th + */ +export function numberAsOrdinal(num: number) { + const int = Math.floor(num); + return `${numeral(int).format('0o')}`; +} diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/utils/round_to_decimal_place.test.ts b/x-pack/plugins/file_data_visualizer/public/application/components/utils/round_to_decimal_place.test.ts new file mode 100644 index 000000000000000..151ae93a93815f0 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/utils/round_to_decimal_place.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { roundToDecimalPlace } from './round_to_decimal_place'; + +describe('roundToDecimalPlace formatter', () => { + it('returns the correct format using default decimal place', () => { + expect(roundToDecimalPlace(12)).toBe(12); + expect(roundToDecimalPlace(12.3)).toBe(12.3); + expect(roundToDecimalPlace(12.34)).toBe(12.34); + expect(roundToDecimalPlace(12.345)).toBe(12.35); + expect(roundToDecimalPlace(12.045)).toBe(12.05); + expect(roundToDecimalPlace(12.005)).toBe(12.01); + expect(roundToDecimalPlace(12.0005)).toBe(12); + expect(roundToDecimalPlace(0.05)).toBe(0.05); + expect(roundToDecimalPlace(0.005)).toBe('5.00e-3'); + expect(roundToDecimalPlace(0.0005)).toBe('5.00e-4'); + expect(roundToDecimalPlace(-0.0005)).toBe('-5.00e-4'); + expect(roundToDecimalPlace(-12.045)).toBe(-12.04); + expect(roundToDecimalPlace(0)).toBe(0); + }); + + it('returns the correct format using specified decimal place', () => { + expect(roundToDecimalPlace(12, 4)).toBe(12); + expect(roundToDecimalPlace(12.3, 4)).toBe(12.3); + expect(roundToDecimalPlace(12.3456, 4)).toBe(12.3456); + expect(roundToDecimalPlace(12.345678, 4)).toBe(12.3457); + expect(roundToDecimalPlace(0.05, 4)).toBe(0.05); + expect(roundToDecimalPlace(0.0005, 4)).toBe(0.0005); + expect(roundToDecimalPlace(0.00005, 4)).toBe('5.00e-5'); + expect(roundToDecimalPlace(-0.00005, 4)).toBe('-5.00e-5'); + expect(roundToDecimalPlace(0, 4)).toBe(0); + }); +}); diff --git a/x-pack/plugins/file_data_visualizer/public/application/components/utils/round_to_decimal_place.ts b/x-pack/plugins/file_data_visualizer/public/application/components/utils/round_to_decimal_place.ts new file mode 100644 index 000000000000000..88ab605a9536985 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/components/utils/round_to_decimal_place.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function roundToDecimalPlace(num?: number, dp: number = 2): number | string { + if (num === undefined) return ''; + if (num % 1 === 0) { + // no decimal place + return num; + } + + if (Math.abs(num) < Math.pow(10, -dp)) { + return Number.parseFloat(String(num)).toExponential(2); + } + const m = Math.pow(10, dp); + return Math.round(num * m) / m; +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts b/x-pack/plugins/file_data_visualizer/public/application/components/utils/utils.ts similarity index 96% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts rename to x-pack/plugins/file_data_visualizer/public/application/components/utils/utils.ts index 49e5da565b92737..1d47e633188c51e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts +++ b/x-pack/plugins/file_data_visualizer/public/application/components/utils/utils.ts @@ -6,8 +6,7 @@ */ import { isEqual } from 'lodash'; -import { AnalysisResult, InputOverrides } from '../../../../../../../file_upload/common'; -import { MB } from '../../../../../../../file_upload/public'; +import { AnalysisResult, InputOverrides, MB } from '../../../../../file_upload/common'; export const DEFAULT_LINES_TO_SAMPLE = 1000; const UPLOAD_SIZE_MB = 5; diff --git a/x-pack/plugins/file_data_visualizer/public/application/file_datavisualizer.tsx b/x-pack/plugins/file_data_visualizer/public/application/file_datavisualizer.tsx new file mode 100644 index 000000000000000..f291076557bb832 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/file_datavisualizer.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import './_index.scss'; +import React, { FC } from 'react'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { getCoreStart, getPluginsStart } from '../kibana_services'; + +// @ts-ignore +import { FileDataVisualizerView } from './components/file_datavisualizer_view/index'; + +export const FileDataVisualizer: FC = () => { + const coreStart = getCoreStart(); + const { data, maps, embeddable, share, security, fileUpload } = getPluginsStart(); + const services = { data, maps, embeddable, share, security, fileUpload, ...coreStart }; + + return ( + + + + ); +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/index.ts b/x-pack/plugins/file_data_visualizer/public/application/index.ts new file mode 100644 index 000000000000000..dba820519af9448 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { FileDataVisualizer } from './file_datavisualizer'; diff --git a/x-pack/plugins/file_data_visualizer/public/application/kibana_context.ts b/x-pack/plugins/file_data_visualizer/public/application/kibana_context.ts new file mode 100644 index 000000000000000..6752c322d42e3e1 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/kibana_context.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart } from 'kibana/public'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import type { FileDataVisualizerStartDependencies } from '../plugin'; + +export type StartServices = CoreStart & FileDataVisualizerStartDependencies; +export const useFileDataVisualizerKibana = () => useKibana(); diff --git a/x-pack/plugins/file_data_visualizer/public/application/shared_imports.ts b/x-pack/plugins/file_data_visualizer/public/application/shared_imports.ts new file mode 100644 index 000000000000000..20481d2fde9be8a --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/shared_imports.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { XJson } from '../../../../../src/plugins/es_ui_shared/public'; +const { collapseLiteralStrings, expandLiteralStrings } = XJson; + +export { XJsonMode } from '@kbn/ace'; +export { collapseLiteralStrings, expandLiteralStrings }; diff --git a/x-pack/plugins/file_data_visualizer/public/application/util/field_types_utils.test.ts b/x-pack/plugins/file_data_visualizer/public/application/util/field_types_utils.test.ts new file mode 100644 index 000000000000000..6f81c0bf4e7d321 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/util/field_types_utils.test.ts @@ -0,0 +1,29 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { JOB_FIELD_TYPES } from '../../../common'; +import { getJobTypeAriaLabel, jobTypeAriaLabels } from './field_types_utils'; + +describe('field type utils', () => { + describe('getJobTypeAriaLabel: Getting a field type aria label by passing what it is stored in constants', () => { + test('should returns all JOB_FIELD_TYPES labels exactly as it is for each correct value', () => { + const keys = Object.keys(JOB_FIELD_TYPES); + const receivedLabels: Record = {}; + const testStorage = jobTypeAriaLabels; + keys.forEach((constant) => { + receivedLabels[constant] = getJobTypeAriaLabel( + JOB_FIELD_TYPES[constant as keyof typeof JOB_FIELD_TYPES] + ); + }); + + expect(receivedLabels).toEqual(testStorage); + }); + test('should returns NULL as JOB_FIELD_TYPES does not contain such a keyword', () => { + expect(getJobTypeAriaLabel('JOB_FIELD_TYPES')).toBe(null); + }); + }); +}); diff --git a/x-pack/plugins/file_data_visualizer/public/application/util/field_types_utils.ts b/x-pack/plugins/file_data_visualizer/public/application/util/field_types_utils.ts new file mode 100644 index 000000000000000..76a5f6ac2011730 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/util/field_types_utils.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { JOB_FIELD_TYPES } from '../../../common'; + +export const jobTypeAriaLabels = { + BOOLEAN: i18n.translate('xpack.fileDataVisualizer.fieldTypeIcon.booleanTypeAriaLabel', { + defaultMessage: 'boolean type', + }), + DATE: i18n.translate('xpack.fileDataVisualizer.fieldTypeIcon.dateTypeAriaLabel', { + defaultMessage: 'date type', + }), + GEO_POINT: i18n.translate('xpack.fileDataVisualizer.fieldTypeIcon.geoPointTypeAriaLabel', { + defaultMessage: '{geoPointParam} type', + values: { + geoPointParam: 'geo point', + }, + }), + IP: i18n.translate('xpack.fileDataVisualizer.fieldTypeIcon.ipTypeAriaLabel', { + defaultMessage: 'ip type', + }), + KEYWORD: i18n.translate('xpack.fileDataVisualizer.fieldTypeIcon.keywordTypeAriaLabel', { + defaultMessage: 'keyword type', + }), + NUMBER: i18n.translate('xpack.fileDataVisualizer.fieldTypeIcon.numberTypeAriaLabel', { + defaultMessage: 'number type', + }), + TEXT: i18n.translate('xpack.fileDataVisualizer.fieldTypeIcon.textTypeAriaLabel', { + defaultMessage: 'text type', + }), + UNKNOWN: i18n.translate('xpack.fileDataVisualizer.fieldTypeIcon.unknownTypeAriaLabel', { + defaultMessage: 'unknown type', + }), +}; + +export const getJobTypeAriaLabel = (type: string) => { + const requestedFieldType = Object.keys(JOB_FIELD_TYPES).find( + (k) => JOB_FIELD_TYPES[k as keyof typeof JOB_FIELD_TYPES] === type + ); + if (requestedFieldType === undefined) { + return null; + } + return jobTypeAriaLabels[requestedFieldType as keyof typeof jobTypeAriaLabels]; +}; diff --git a/x-pack/plugins/file_data_visualizer/public/application/util/get_max_bytes.ts b/x-pack/plugins/file_data_visualizer/public/application/util/get_max_bytes.ts new file mode 100644 index 000000000000000..821a94bf5166d03 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/application/util/get_max_bytes.ts @@ -0,0 +1,14 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getPluginsStart } from '../../kibana_services'; + +// expose the fileUpload plugin's getMaxBytesFormatted for use in ML +// so ML doesn't need to depend on the fileUpload plugin for this one function +export function getMaxBytesFormatted() { + return getPluginsStart().fileUpload.getMaxBytesFormatted(); +} diff --git a/x-pack/plugins/file_data_visualizer/public/index.ts b/x-pack/plugins/file_data_visualizer/public/index.ts new file mode 100644 index 000000000000000..64a81936dbbdeba --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/index.ts @@ -0,0 +1,14 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FileDataVisualizerPlugin } from './plugin'; + +export function plugin() { + return new FileDataVisualizerPlugin(); +} + +export { FileDataVisualizerPluginStart } from './plugin'; diff --git a/x-pack/plugins/file_data_visualizer/public/kibana_services.ts b/x-pack/plugins/file_data_visualizer/public/kibana_services.ts new file mode 100644 index 000000000000000..6a5fe85c72477e5 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/kibana_services.ts @@ -0,0 +1,19 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart } from 'kibana/public'; +import { FileDataVisualizerStartDependencies } from './plugin'; + +let coreStart: CoreStart; +let pluginsStart: FileDataVisualizerStartDependencies; +export function setStartServices(core: CoreStart, plugins: FileDataVisualizerStartDependencies) { + coreStart = core; + pluginsStart = plugins; +} + +export const getCoreStart = () => coreStart; +export const getPluginsStart = () => pluginsStart; diff --git a/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/index.ts b/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/index.ts new file mode 100644 index 000000000000000..99dbb6d3746ce35 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpSetup } from 'src/core/public'; +import { FileDataVisualizer } from '../application'; +import { getCoreStart } from '../kibana_services'; + +let loadModulesPromise: Promise; + +interface LazyLoadedModules { + FileDataVisualizer: typeof FileDataVisualizer; + getHttp: () => HttpSetup; +} + +export async function lazyLoadModules(): Promise { + if (typeof loadModulesPromise !== 'undefined') { + return loadModulesPromise; + } + + loadModulesPromise = new Promise(async (resolve) => { + const lazyImports = await import('./lazy'); + + resolve({ + ...lazyImports, + getHttp: () => getCoreStart().http, + }); + }); + return loadModulesPromise; +} diff --git a/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/lazy/index.ts new file mode 100644 index 000000000000000..4229b95f3aaad80 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/lazy_load_bundle/lazy/index.ts @@ -0,0 +1,8 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { FileDataVisualizer } from '../../application'; diff --git a/x-pack/plugins/file_data_visualizer/public/plugin.ts b/x-pack/plugins/file_data_visualizer/public/plugin.ts new file mode 100644 index 000000000000000..a94c0fce45cd405 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/public/plugin.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart } from 'kibana/public'; +import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; +import type { SharePluginStart } from '../../../../src/plugins/share/public'; +import { Plugin } from '../../../../src/core/public'; + +import { setStartServices } from './kibana_services'; +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import type { FileUploadPluginStart } from '../../file_upload/public'; +import type { MapsStartApi } from '../../maps/public'; +import type { SecurityPluginSetup } from '../../security/public'; +import { getFileDataVisualizerComponent } from './api'; +import { getMaxBytesFormatted } from './application/util/get_max_bytes'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface FileDataVisualizerSetupDependencies {} +export interface FileDataVisualizerStartDependencies { + data: DataPublicPluginStart; + fileUpload: FileUploadPluginStart; + maps: MapsStartApi; + embeddable: EmbeddableStart; + security?: SecurityPluginSetup; + share: SharePluginStart; +} + +export type FileDataVisualizerPluginSetup = ReturnType; +export type FileDataVisualizerPluginStart = ReturnType; + +export class FileDataVisualizerPlugin + implements + Plugin< + FileDataVisualizerPluginSetup, + FileDataVisualizerPluginStart, + FileDataVisualizerSetupDependencies, + FileDataVisualizerStartDependencies + > { + public setup() {} + + public start(core: CoreStart, plugins: FileDataVisualizerStartDependencies) { + setStartServices(core, plugins); + return { getFileDataVisualizerComponent, getMaxBytesFormatted }; + } +} diff --git a/x-pack/plugins/file_data_visualizer/server/index.ts b/x-pack/plugins/file_data_visualizer/server/index.ts new file mode 100644 index 000000000000000..43067dbe99d0d76 --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/server/index.ts @@ -0,0 +1,10 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FileDataVisualizerPlugin } from './plugin'; + +export const plugin = () => new FileDataVisualizerPlugin(); diff --git a/x-pack/plugins/file_data_visualizer/server/plugin.ts b/x-pack/plugins/file_data_visualizer/server/plugin.ts new file mode 100644 index 000000000000000..f6893b7edaa535b --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/server/plugin.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Plugin } from 'src/core/server'; + +export class FileDataVisualizerPlugin implements Plugin { + setup() {} + start() {} +} diff --git a/x-pack/plugins/file_data_visualizer/tsconfig.json b/x-pack/plugins/file_data_visualizer/tsconfig.json new file mode 100644 index 000000000000000..2d668bcaa20456f --- /dev/null +++ b/x-pack/plugins/file_data_visualizer/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "server/**/*"], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../security/tsconfig.json" }, + { "path": "../file_upload/tsconfig.json" }, + { "path": "../maps/tsconfig.json" }, + { "path": "../../../src/plugins/embeddable/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/file_upload/common/constants.ts b/x-pack/plugins/file_upload/common/constants.ts index ea36e51466703fa..977f969647658d5 100644 --- a/x-pack/plugins/file_upload/common/constants.ts +++ b/x-pack/plugins/file_upload/common/constants.ts @@ -16,4 +16,4 @@ export const FILE_SIZE_DISPLAY_FORMAT = '0,0.[0] b'; // Value to use in the Elasticsearch index mapping meta data to identify the // index as having been created by the ML File Data Visualizer. -export const INDEX_META_DATA_CREATED_BY = 'ml-file-data-visualizer'; +export const INDEX_META_DATA_CREATED_BY = 'file-data-visualizer'; diff --git a/x-pack/plugins/file_upload/common/types.ts b/x-pack/plugins/file_upload/common/types.ts index 11cf4ac3615bfaf..e10b9e90a71d8e4 100644 --- a/x-pack/plugins/file_upload/common/types.ts +++ b/x-pack/plugins/file_upload/common/types.ts @@ -6,11 +6,7 @@ */ import type { estypes } from '@elastic/elasticsearch'; -import { ES_FIELD_TYPES } from '../../../../src/plugins/data/common'; - -export interface HasImportPermission { - hasImportPermission: boolean; -} +import { ES_FIELD_TYPES } from 'src/plugins/data/common'; export interface InputOverrides { [key: string]: string | undefined; @@ -75,6 +71,28 @@ export interface FindFileStructureResponse { should_trim_fields?: boolean; } +export interface FindFileStructureErrorResponse { + body: { + statusCode: number; + error: string; + message: string; + attributes?: ErrorAttribute; + }; + name: string; +} + +interface ErrorAttribute { + body: { + error: { + suppressed: Array<{ reason: string }>; + }; + }; +} + +export interface HasImportPermission { + hasImportPermission: boolean; +} + export type InputData = any[]; export interface ImportResponse { diff --git a/x-pack/plugins/file_upload/kibana.json b/x-pack/plugins/file_upload/kibana.json index a1c585e5343335c..6f93874cdbcaafc 100644 --- a/x-pack/plugins/file_upload/kibana.json +++ b/x-pack/plugins/file_upload/kibana.json @@ -4,7 +4,17 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["data", "usageCollection"], - "optionalPlugins": ["security"], - "requiredBundles": ["kibanaReact"] + "requiredPlugins": [ + "data", + "usageCollection" + ], + "optionalPlugins": [ + "security" + ], + "requiredBundles": [ + "kibanaReact" + ], + "extraPublicDirs": [ + "common" + ] } diff --git a/x-pack/plugins/file_upload/public/api/index.ts b/x-pack/plugins/file_upload/public/api/index.ts index 281537cbbde167a..23eeb9abde324b1 100644 --- a/x-pack/plugins/file_upload/public/api/index.ts +++ b/x-pack/plugins/file_upload/public/api/index.ts @@ -6,22 +6,32 @@ */ import React from 'react'; -import { FileUploadComponentProps, lazyLoadFileUploadModules } from '../lazy_load_bundle'; +import { FileUploadComponentProps, lazyLoadModules } from '../lazy_load_bundle'; import type { IImporter, ImportFactoryOptions } from '../importer'; -import { HasImportPermission } from '../../common'; +import type { HasImportPermission, FindFileStructureResponse } from '../../common'; +import type { getMaxBytes, getMaxBytesFormatted } from '../importer/get_max_bytes'; export interface FileUploadStartApi { - getFileUploadComponent(): Promise>; - importerFactory(format: string, options: ImportFactoryOptions): Promise; - getMaxBytes(): number; - getMaxBytesFormatted(): string; - hasImportPermission(params: HasImportPermissionParams): Promise; + getFileUploadComponent(): ReturnType; + importerFactory: typeof importerFactory; + getMaxBytes: typeof getMaxBytes; + getMaxBytesFormatted: typeof getMaxBytesFormatted; + hasImportPermission: typeof hasImportPermission; + checkIndexExists: typeof checkIndexExists; + getTimeFieldRange: typeof getTimeFieldRange; + analyzeFile: typeof analyzeFile; +} + +export interface GetTimeFieldRangeResponse { + success: boolean; + start: { epoch: number; string: string }; + end: { epoch: number; string: string }; } export async function getFileUploadComponent(): Promise< React.ComponentType > { - const fileUploadModules = await lazyLoadFileUploadModules(); + const fileUploadModules = await lazyLoadModules(); return fileUploadModules.JsonUploadAndParse; } @@ -29,7 +39,7 @@ export async function importerFactory( format: string, options: ImportFactoryOptions ): Promise { - const fileUploadModules = await lazyLoadFileUploadModules(); + const fileUploadModules = await lazyLoadModules(); return fileUploadModules.importerFactory(format, options); } @@ -39,8 +49,22 @@ interface HasImportPermissionParams { indexName?: string; } +export async function analyzeFile( + file: string, + params: Record = {} +): Promise { + const { getHttp } = await lazyLoadModules(); + const body = JSON.stringify(file); + return await getHttp().fetch({ + path: `/internal/file_data_visualizer/analyze_file`, + method: 'POST', + body, + query: params, + }); +} + export async function hasImportPermission(params: HasImportPermissionParams): Promise { - const fileUploadModules = await lazyLoadFileUploadModules(); + const fileUploadModules = await lazyLoadModules(); try { const resp = await fileUploadModules.getHttp().fetch({ path: `/internal/file_upload/has_import_permission`, @@ -52,3 +76,29 @@ export async function hasImportPermission(params: HasImportPermissionParams): Pr return false; } } + +export async function checkIndexExists( + index: string, + params: Record = {} +): Promise { + const body = JSON.stringify({ index }); + const fileUploadModules = await lazyLoadModules(); + const { exists } = await fileUploadModules.getHttp().fetch<{ exists: boolean }>({ + path: `/internal/file_upload/index_exists`, + method: 'POST', + body, + query: params, + }); + return exists; +} + +export async function getTimeFieldRange(index: string, query: unknown, timeFieldName?: string) { + const body = JSON.stringify({ index, timeFieldName, query }); + + const fileUploadModules = await lazyLoadModules(); + return await fileUploadModules.getHttp().fetch({ + path: `/internal/file_upload/time_field_range`, + method: 'POST', + body, + }); +} diff --git a/x-pack/plugins/file_upload/public/components/geojson_upload_form/geojson_file_picker.tsx b/x-pack/plugins/file_upload/public/components/geojson_upload_form/geojson_file_picker.tsx index 2f31bc47b899cd8..6cd55e3a0a74a08 100644 --- a/x-pack/plugins/file_upload/public/components/geojson_upload_form/geojson_file_picker.tsx +++ b/x-pack/plugins/file_upload/public/components/geojson_upload_form/geojson_file_picker.tsx @@ -9,7 +9,7 @@ import React, { Component } from 'react'; import { EuiFilePicker, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { MB } from '../../../common'; -import { getMaxBytesFormatted } from '../../get_max_bytes'; +import { getMaxBytesFormatted } from '../../importer/get_max_bytes'; import { validateFile } from '../../importer'; import { GeoJsonImporter, diff --git a/x-pack/plugins/file_upload/public/get_max_bytes.ts b/x-pack/plugins/file_upload/public/importer/get_max_bytes.ts similarity index 91% rename from x-pack/plugins/file_upload/public/get_max_bytes.ts rename to x-pack/plugins/file_upload/public/importer/get_max_bytes.ts index 2e002e65248c90c..f1ca532692e77f5 100644 --- a/x-pack/plugins/file_upload/public/get_max_bytes.ts +++ b/x-pack/plugins/file_upload/public/importer/get_max_bytes.ts @@ -5,7 +5,6 @@ * 2.0. */ -// @ts-ignore import numeral from '@elastic/numeral'; import { MAX_FILE_SIZE, @@ -13,8 +12,8 @@ import { ABSOLUTE_MAX_FILE_SIZE_BYTES, FILE_SIZE_DISPLAY_FORMAT, UI_SETTING_MAX_FILE_SIZE, -} from '../common'; -import { getUiSettings } from './kibana_services'; +} from '../../common'; +import { getUiSettings } from '../kibana_services'; export function getMaxBytes() { const maxFileSize = getUiSettings().get(UI_SETTING_MAX_FILE_SIZE, MAX_FILE_SIZE); diff --git a/x-pack/plugins/file_upload/public/importer/importer.ts b/x-pack/plugins/file_upload/public/importer/importer.ts index 4a87d67d0616bac..49324c8f360efd2 100644 --- a/x-pack/plugins/file_upload/public/importer/importer.ts +++ b/x-pack/plugins/file_upload/public/importer/importer.ts @@ -260,7 +260,7 @@ export function callImportRoute({ }); return getHttp().fetch({ - path: `/api/file_upload/import`, + path: `/internal/file_upload/import`, method: 'POST', query, body, diff --git a/x-pack/plugins/file_upload/public/importer/validate_file.ts b/x-pack/plugins/file_upload/public/importer/validate_file.ts index 4c7fe704d8afadf..60d93ad552d0d31 100644 --- a/x-pack/plugins/file_upload/public/importer/validate_file.ts +++ b/x-pack/plugins/file_upload/public/importer/validate_file.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { getMaxBytes, getMaxBytesFormatted } from '../get_max_bytes'; +import { getMaxBytes, getMaxBytesFormatted } from './get_max_bytes'; export function validateFile(file: File, types: string[]) { if (file.size > getMaxBytes()) { diff --git a/x-pack/plugins/file_upload/public/index.ts b/x-pack/plugins/file_upload/public/index.ts index bb69a1b2efb0536..792568e9c11adae 100644 --- a/x-pack/plugins/file_upload/public/index.ts +++ b/x-pack/plugins/file_upload/public/index.ts @@ -11,8 +11,6 @@ export function plugin() { return new FileUploadPlugin(); } -export * from '../common'; - export * from './importer/types'; export { FileUploadPluginStart } from './plugin'; diff --git a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts index e1e00bee3715986..9d89b6b761e255b 100644 --- a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts @@ -36,7 +36,7 @@ interface LazyLoadedFileUploadModules { getHttp: () => HttpStart; } -export async function lazyLoadFileUploadModules(): Promise { +export async function lazyLoadModules(): Promise { if (typeof loadModulesPromise !== 'undefined') { return loadModulesPromise; } diff --git a/x-pack/plugins/file_upload/public/plugin.ts b/x-pack/plugins/file_upload/public/plugin.ts index a4e386b85e1826b..19306fadfd61c17 100644 --- a/x-pack/plugins/file_upload/public/plugin.ts +++ b/x-pack/plugins/file_upload/public/plugin.ts @@ -11,10 +11,13 @@ import { getFileUploadComponent, importerFactory, hasImportPermission, + checkIndexExists, + getTimeFieldRange, + analyzeFile, } from './api'; import { setStartServices } from './kibana_services'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; -import { getMaxBytes, getMaxBytesFormatted } from './get_max_bytes'; +import { getMaxBytes, getMaxBytesFormatted } from './importer/get_max_bytes'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface FileUploadSetupDependencies {} @@ -43,6 +46,9 @@ export class FileUploadPlugin getMaxBytes, getMaxBytesFormatted, hasImportPermission, + checkIndexExists, + getTimeFieldRange, + analyzeFile, }; } } diff --git a/x-pack/plugins/file_upload/server/get_time_field_range.ts b/x-pack/plugins/file_upload/server/get_time_field_range.ts new file mode 100644 index 000000000000000..66a428128cbe1a6 --- /dev/null +++ b/x-pack/plugins/file_upload/server/get_time_field_range.ts @@ -0,0 +1,54 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { IScopedClusterClient } from 'kibana/server'; +export async function getTimeFieldRange( + client: IScopedClusterClient, + index: string[] | string, + timeFieldName: string, + query: any +): Promise<{ + success: boolean; + start: { epoch: number; string: string }; + end: { epoch: number; string: string }; +}> { + const obj = { success: true, start: { epoch: 0, string: '' }, end: { epoch: 0, string: '' } }; + + const { + body: { aggregations }, + } = await client.asCurrentUser.search({ + index, + size: 0, + body: { + ...(query ? { query } : {}), + aggs: { + earliest: { + min: { + field: timeFieldName, + }, + }, + latest: { + max: { + field: timeFieldName, + }, + }, + }, + }, + }); + + if (aggregations && aggregations.earliest && aggregations.latest) { + // @ts-expect-error fix search aggregation response + obj.start.epoch = aggregations.earliest.value; + // @ts-expect-error fix search aggregation response + obj.start.string = aggregations.earliest.value_as_string; + + // @ts-expect-error fix search aggregation response + obj.end.epoch = aggregations.latest.value; + // @ts-expect-error fix search aggregation response + obj.end.string = aggregations.latest.value_as_string; + } + return obj; +} diff --git a/x-pack/plugins/file_upload/server/routes.ts b/x-pack/plugins/file_upload/server/routes.ts index 6d7eb77f3906901..f2e796ec53ce0b5 100644 --- a/x-pack/plugins/file_upload/server/routes.ts +++ b/x-pack/plugins/file_upload/server/routes.ts @@ -16,11 +16,12 @@ import { Settings, } from '../common'; import { wrapError } from './error_wrapper'; -import { analyzeFile } from './analyze_file'; import { importDataProvider } from './import_data'; +import { getTimeFieldRange } from './get_time_field_range'; +import { analyzeFile } from './analyze_file'; import { updateTelemetry } from './telemetry'; -import { analyzeFileQuerySchema, importFileBodySchema, importFileQuerySchema } from './schemas'; +import { importFileBodySchema, importFileQuerySchema, analyzeFileQuerySchema } from './schemas'; import { CheckPrivilegesPayload } from '../../security/server'; import { StartDeps } from './types'; @@ -92,7 +93,7 @@ export function fileUploadRoutes(coreSetup: CoreSetup, logge /** * @apiGroup FileDataVisualizer * - * @api {post} /api/file_upload/analyze_file Analyze file data + * @api {post} /internal/file_upload/analyze_file Analyze file data * @apiName AnalyzeFile * @apiDescription Performs analysis of the file data. * @@ -100,7 +101,7 @@ export function fileUploadRoutes(coreSetup: CoreSetup, logge */ router.post( { - path: '/api/file_upload/analyze_file', + path: '/internal/file_data_visualizer/analyze_file', validate: { body: schema.any(), query: analyzeFileQuerySchema, @@ -130,7 +131,7 @@ export function fileUploadRoutes(coreSetup: CoreSetup, logge /** * @apiGroup FileDataVisualizer * - * @api {post} /api/file_upload/import Import file data + * @api {post} /internal/file_upload/import Import file data * @apiName ImportFile * @apiDescription Imports file data into elasticsearch index. * @@ -139,7 +140,7 @@ export function fileUploadRoutes(coreSetup: CoreSetup, logge */ router.post( { - path: '/api/file_upload/import', + path: '/internal/file_upload/import', validate: { query: importFileQuerySchema, body: importFileBodySchema, @@ -180,4 +181,90 @@ export function fileUploadRoutes(coreSetup: CoreSetup, logge } } ); + + /** + * @apiGroup FileDataVisualizer + * + * @api {post} /internal/file_upload/index_exists ES Field caps wrapper checks if index exists + * @apiName IndexExists + */ + router.post( + { + path: '/internal/file_upload/index_exists', + validate: { + body: schema.object({ index: schema.string() }), + }, + options: { + tags: ['access:fileUpload:analyzeFile'], + }, + }, + async (context, request, response) => { + try { + const { index } = request.body; + + const options = { + index: [index], + fields: ['*'], + ignore_unavailable: true, + allow_no_indices: true, + }; + + const { body } = await context.core.elasticsearch.client.asCurrentUser.fieldCaps(options); + const exists = Array.isArray(body.indices) && body.indices.length !== 0; + return response.ok({ + body: { exists }, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + } + ); + + /** + * @apiGroup FileDataVisualizer + * + * @api {post} /internal/file_upload/time_field_range Get time field range + * @apiName GetTimeFieldRange + * @apiDescription Returns the time range for the given index and query using the specified time range. + * + * @apiSchema (body) getTimeFieldRangeSchema + * + * @apiSuccess {Object} start start of time range with epoch and string properties. + * @apiSuccess {Object} end end of time range with epoch and string properties. + */ + router.post( + { + path: '/internal/file_upload/time_field_range', + validate: { + body: schema.object({ + /** Index or indexes for which to return the time range. */ + index: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), + /** Name of the time field in the index. */ + timeFieldName: schema.string(), + /** Query to match documents in the index(es). */ + query: schema.maybe(schema.any()), + }), + }, + options: { + tags: ['access:fileUpload:analyzeFile'], + }, + }, + async (context, request, response) => { + try { + const { index, timeFieldName, query } = request.body; + const resp = await getTimeFieldRange( + context.core.elasticsearch.client, + index, + timeFieldName, + query + ); + + return response.ok({ + body: resp, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + } + ); } diff --git a/x-pack/plugins/file_upload/tsconfig.json b/x-pack/plugins/file_upload/tsconfig.json index 887a05af31174b5..3e146d76fbb9020 100644 --- a/x-pack/plugins/file_upload/tsconfig.json +++ b/x-pack/plugins/file_upload/tsconfig.json @@ -13,5 +13,6 @@ { "path": "../../../src/plugins/data/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, { "path": "../security/tsconfig.json" }, + { "path": "../../../src/plugins/embeddable/tsconfig.json" }, ] } diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index 4955c1af5674def..f61ab17646b65dd 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -10,7 +10,7 @@ "data", "cloud", "features", - "fileUpload", + "fileDataVisualizer", "licensing", "share", "embeddable", diff --git a/x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts b/x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts index 77381c8728a480c..ad47e84319e4a95 100644 --- a/x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts +++ b/x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts @@ -24,5 +24,5 @@ export const createMlStartDepsMock = () => ({ maps: jest.fn(), lens: lensPluginMock.createStartContract(), triggersActionsUi: triggersActionsUiMock.createStart(), - fileUpload: jest.fn(), + fileDataVisualizer: jest.fn(), }); diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 5f72d49e4672e91..e2fbcc77f2767d8 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -82,7 +82,7 @@ const App: FC = ({ coreStart, deps, appMountParams }) => { embeddable: deps.embeddable, maps: deps.maps, triggersActionsUi: deps.triggersActionsUi, - fileUpload: deps.fileUpload, + fileDataVisualizer: deps.fileDataVisualizer, ...coreStart, }; @@ -125,7 +125,7 @@ export const renderApp = ( security: deps.security, urlGenerators: deps.share.urlGenerators, maps: deps.maps, - fileUpload: deps.fileUpload, + fileDataVisualizer: deps.fileDataVisualizer, }); appMountParams.onAppLeave((actions) => actions.default()); diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx index 3a6979d021c8bf5..0f381fb7acee91d 100644 --- a/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx +++ b/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx @@ -161,14 +161,16 @@ export const MainTabs: FC = ({ tabId, disableLinks }) => { const defaultPathId = (TAB_DATA[id].pathId || id) as MlUrlGeneratorState['page']; return disabled ? ( - - {tab.name} - +
+ + {tab.name} + +
) : (
{ licenseManagement, http: { basePath }, docLinks, - fileUpload, + fileDataVisualizer, }, } = useMlKibana(); @@ -68,12 +68,12 @@ export const DatavisualizerSelector: FC = () => { licenseManagement.enabled === true && isFullLicense() === false; - if (fileUpload === undefined) { + if (fileDataVisualizer === undefined) { // eslint-disable-next-line no-console - console.error('File upload plugin not available'); + console.error('File data visualizer plugin not available'); return null; } - const maxFileSize = fileUpload.getMaxBytesFormatted(); + const maxFileSize = fileDataVisualizer.getMaxBytesFormatted(); return ( diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss deleted file mode 100644 index d0af6d3f01d2f2c..000000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss +++ /dev/null @@ -1,150 +0,0 @@ -.card-container { - display: inline-grid; - display: -ms-inline-grid; - padding: 0 10px 10px 0; -} - -.ml-field-data-card { - // These styles should all be removed once the file data visualizer is using - // the same field_data_card component as the index based data visualizer. - height: 408px; - box-shadow: none; - border-color: $euiBorderColor; - - // Note the names of these styles need to match the type of the field they are displaying. - .boolean { - color: $euiColorVis5; - border-color: $euiColorVis5; - } - - .date { - color: $euiColorVis7; - border-color: $euiColorVis7; - } - - .document_count { - color: $euiColorVis2; - border-color: $euiColorVis2; - } - - .geo_point { - color: $euiColorVis8; - border-color: $euiColorVis8; - } - - .ip { - color: $euiColorVis3; - border-color: $euiColorVis3; - } - - .keyword { - color: $euiColorVis0; - border-color: $euiColorVis0; - } - - .number { - color: $euiColorVis1; - border-color: $euiColorVis1; - } - - .text { - color: $euiColorVis9; - border-color: $euiColorVis9; - } - - .type-other, - .unknown { - color: $euiColorVis6; - border-color: $euiColorVis6; - } - - // Use euiPanel styling - @include euiPanel($selector: '.card-contents'); - - .stats { - text-align: center; - } - - .stat { - padding-bottom: 6px; - } - - .stat.heading { - padding-bottom: 0; - } - - .stat.min, - .stat.max, - .stat.median { - width: 30%; - display: inline-block; - } - - .stat.min.value, - .stat.max.value, - .stat.median.value { - font-size: $euiFontSizeS; - @include euiTextTruncate; - } - - .valueWrapper { - display: inline; - } - - .not-exist-message { - padding: 50px 30px 0 30px; - text-align: center; - } - - .sampled-message { - font-size: 11px; - color: #555555; - text-align: center; - padding-top: 3px; - } - - .text-code { - font-family: $euiCodeFontFamily; - } - - .details-select { - text-align: center; - margin-top: 5px; - margin-bottom: 5px; - } - - .details-container { - padding-top: 5px; - } - - .top-value { - height: 21px; - font-size: 13px; - - .field-label { - @include euiTextTruncate; - - display: inline-block; - width: 100px; - text-align: right; - } - - .count-label { - display: inline-block; - width: 70px; - text-align: left; - overflow: hidden; - text-overflow: ellipsis; - } - - .top-value-bar-holder { - display: inline-block; - width: 160px; - } - - .top-value-bar { - height: 15px; - min-width: 3px; - } - } -} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_fields_stats.scss b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_fields_stats.scss deleted file mode 100644 index 5decacfe1b7b87e..000000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_fields_stats.scss +++ /dev/null @@ -1,6 +0,0 @@ -.fields-stats { - padding: 10px; -} -.field { - margin-bottom: 10px; -} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx index a05677918da7231..3b4cfbf33fbfcdc 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx @@ -5,34 +5,32 @@ * 2.0. */ -import React, { FC, Fragment } from 'react'; -import { IUiSettingsClient } from 'kibana/public'; +import React, { FC, Fragment, useState, useEffect } from 'react'; import { useTimefilter } from '../../contexts/kibana'; import { NavigationMenu } from '../../components/navigation_menu'; -import { getIndexPatternsContract } from '../../util/index_utils'; import { HelpMenu } from '../../components/help_menu'; import { useMlKibana } from '../../contexts/kibana'; -// @ts-ignore -import { FileDataVisualizerView } from './components/file_datavisualizer_view/index'; - -export interface FileDataVisualizerPageProps { - kibanaConfig: IUiSettingsClient; -} - -export const FileDataVisualizerPage: FC = ({ kibanaConfig }) => { +export const FileDataVisualizerPage: FC = () => { useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); - const indexPatterns = getIndexPatternsContract(); const { - services: { docLinks }, + services: { docLinks, fileDataVisualizer }, } = useMlKibana(); - const helpLink = docLinks.links.ml.guide; + const [FileDataVisualizer, setFileDataVisualizer] = useState | null>(null); + + useEffect(() => { + if (fileDataVisualizer !== undefined) { + const { getFileDataVisualizerComponent } = fileDataVisualizer; + getFileDataVisualizerComponent().then(setFileDataVisualizer); + } + }, []); + return ( - - + {FileDataVisualizer} + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/index.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/index.tsx diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/field_type_filter.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/field_type_filter.tsx index 7bc7260acf5441c..15ddf00c4e1d3c6 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/field_type_filter.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/field_type_filter.tsx @@ -13,7 +13,7 @@ import { FieldTypeIcon } from '../../../../components/field_type_icon'; import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; import type { MlJobFieldType } from '../../../../../../common/types/field_types'; -export const ML_JOB_FIELD_TYPES_OPTIONS = { +const ML_JOB_FIELD_TYPES_OPTIONS = { [ML_JOB_FIELD_TYPES.BOOLEAN]: { name: 'Boolean', icon: 'tokenBoolean' }, [ML_JOB_FIELD_TYPES.DATE]: { name: 'Date', icon: 'tokenDate' }, [ML_JOB_FIELD_TYPES.GEO_POINT]: { name: 'Geo point', icon: 'tokenGeo' }, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts index e0944711033a7a1..d35b0ae9688cf20 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts @@ -7,7 +7,6 @@ export { BooleanContent } from './boolean_content'; export { DateContent } from './date_content'; -export { GeoPointContent } from '../../../file_based/components/expanded_row/geo_point_content/geo_point_content'; export { KeywordContent } from './keyword_content'; export { IpContent } from './ip_content'; export { NumberContent } from './number_content'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/data_visualizer_stats_table.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/data_visualizer_stats_table.tsx index 2a6a681c6321073..2003d07efca820f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/data_visualizer_stats_table.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/data_visualizer_stats_table.tsx @@ -38,7 +38,6 @@ import { FileBasedFieldVisConfig, isIndexBasedFieldVisConfig, } from './types/field_vis_config'; -import { FileBasedNumberContentPreview } from '../file_based/components/field_data_row'; import { BooleanContentPreview } from './components/field_data_row'; const FIELD_NAME = 'fieldName'; @@ -224,8 +223,6 @@ export const DataVisualizerTable = ({ if (item.type === ML_JOB_FIELD_TYPES.NUMBER) { if (isIndexBasedFieldVisConfig(item) && item.stats?.distribution !== undefined) { return ; - } else { - return ; } } diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx index ae3b35bbb2b9177..5b16bf8352b27b7 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx @@ -38,7 +38,7 @@ export const fileBasedRouteFactory = ( ], }); -const PageWrapper: FC = ({ location, deps }) => { +const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; const { context } = useResolver(undefined, undefined, deps.config, { @@ -47,9 +47,10 @@ const PageWrapper: FC = ({ location, deps }) => { checkFindFileStructurePrivilege: () => checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage), }); + return ( - + ); }; diff --git a/x-pack/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/plugins/ml/public/application/util/dependency_cache.ts index 215f087020d6fa3..759d0dcc6874176 100644 --- a/x-pack/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/plugins/ml/public/application/util/dependency_cache.ts @@ -23,7 +23,7 @@ import type { IndexPatternsContract, DataPublicPluginStart } from 'src/plugins/d import type { SharePluginStart } from 'src/plugins/share/public'; import type { SecurityPluginSetup } from '../../../../security/public'; import type { MapsStartApi } from '../../../../maps/public'; -import type { FileUploadPluginStart } from '../../../../file_upload/public'; +import type { FileDataVisualizerPluginStart } from '../../../../file_data_visualizer/public'; export interface DependencyCache { timefilter: DataPublicPluginSetup['query']['timefilter'] | null; @@ -44,7 +44,7 @@ export interface DependencyCache { i18n: I18nStart | null; urlGenerators: SharePluginStart['urlGenerators'] | null; maps: MapsStartApi | null; - fileUpload: FileUploadPluginStart | null; + fileDataVisualizer: FileDataVisualizerPluginStart | null; } const cache: DependencyCache = { @@ -66,7 +66,7 @@ const cache: DependencyCache = { i18n: null, urlGenerators: null, maps: null, - fileUpload: null, + fileDataVisualizer: null, }; export function setDependencyCache(deps: Partial) { @@ -87,7 +87,7 @@ export function setDependencyCache(deps: Partial) { cache.security = deps.security || null; cache.i18n = deps.i18n || null; cache.urlGenerators = deps.urlGenerators || null; - cache.fileUpload = deps.fileUpload || null; + cache.fileDataVisualizer = deps.fileDataVisualizer || null; } export function getTimefilter() { @@ -214,9 +214,9 @@ export function clearCache() { }); } -export function getFileUpload() { - if (cache.fileUpload === null) { - throw new Error("fileUpload hasn't been initialized"); +export function getFileDataVisualizer() { + if (cache.fileDataVisualizer === null) { + throw new Error("fileDataVisualizer hasn't been initialized"); } - return cache.fileUpload; + return cache.fileDataVisualizer; } diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index f6d5da92f5e7154..1ab47256b2c2a40 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -52,7 +52,7 @@ import { TriggersAndActionsUIPublicPluginStart, } from '../../triggers_actions_ui/public'; import { registerMlAlerts } from './alerting/register_ml_alerts'; -import { FileUploadPluginStart } from '../../file_upload/public'; +import { FileDataVisualizerPluginStart } from '../../file_data_visualizer/public'; export interface MlStartDependencies { data: DataPublicPluginStart; @@ -64,7 +64,7 @@ export interface MlStartDependencies { maps?: MapsStartApi; lens?: LensPublicStart; triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; - fileUpload: FileUploadPluginStart; + fileDataVisualizer: FileDataVisualizerPluginStart; } export interface MlSetupDependencies { @@ -121,7 +121,7 @@ export class MlPlugin implements Plugin { lens: pluginsStart.lens, kibanaVersion, triggersActionsUi: pluginsStart.triggersActionsUi, - fileUpload: pluginsStart.fileUpload, + fileDataVisualizer: pluginsStart.fileDataVisualizer, }, params ); diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index 6b396b1c5964295..d887cfc88525326 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -24,7 +24,7 @@ { "path": "../../../src/plugins/index_pattern_management/tsconfig.json" }, { "path": "../cloud/tsconfig.json" }, { "path": "../features/tsconfig.json" }, - { "path": "../file_upload/tsconfig.json" }, + { "path": "../file_data_visualizer/tsconfig.json" }, { "path": "../license_management/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, { "path": "../maps/tsconfig.json" }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0b812c315d94de5..f0caa03442c0916 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13598,8 +13598,6 @@ "xpack.ml.datavisualizer.dataGrid.showDistributionsAriaLabel": "分布を表示", "xpack.ml.datavisualizer.dataGrid.typeColumnName": "型", "xpack.ml.datavisualizer.dataLoader.internalServerErrorMessage": "インデックス {index} のデータの読み込み中にエラーが発生。{message}。リクエストがタイムアウトした可能性があります。小さなサンプルサイズを使うか、時間範囲を狭めてみてください。", - "xpack.ml.dataVisualizer.fileBased.fieldNameSelect": "フィールド名", - "xpack.ml.dataVisualizer.fileBased.fieldTypeSelect": "フィールド型", "xpack.ml.dataVisualizer.fileBasedLabel": "ファイル", "xpack.ml.dataVisualizer.indexBased.fieldNameSelect": "フィールド名", "xpack.ml.dataVisualizer.indexBased.fieldTypeSelect": "フィールド型", @@ -13776,163 +13774,6 @@ "xpack.ml.fieldTypeIcon.numberTypeAriaLabel": "数字タイプ", "xpack.ml.fieldTypeIcon.textTypeAriaLabel": "テキストタイプ", "xpack.ml.fieldTypeIcon.unknownTypeAriaLabel": "不明なタイプ", - "xpack.ml.fileDatavisualizer.aboutPanel.analyzingDataTitle": "データを分析中", - "xpack.ml.fileDatavisualizer.aboutPanel.selectOrDragAndDropFileDescription": "ファイルを選択するかドラッグ &amp; ドロップしてください", - "xpack.ml.fileDatavisualizer.addCombinedFieldsLabel": "結合されたフィールドを追加", - "xpack.ml.fileDatavisualizer.advancedImportSettings.createIndexPatternLabel": "インデックスパターンを作成", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexNameAriaLabel": "インデックス名、必須フィールド", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexNameLabel": "インデックス名", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexNamePlaceholder": "インデックス名", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexPatternNameLabel": "インデックスパターン名", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexSettingsLabel": "インデックス設定", - "xpack.ml.fileDatavisualizer.advancedImportSettings.ingestPipelineLabel": "パイプラインを投入", - "xpack.ml.fileDatavisualizer.advancedImportSettings.mappingsLabel": "マッピング", - "xpack.ml.fileDatavisualizer.analysisSummary.analyzedLinesNumberTitle": "分析した行数", - "xpack.ml.fileDatavisualizer.analysisSummary.delimiterTitle": "区切り記号", - "xpack.ml.fileDatavisualizer.analysisSummary.formatTitle": "フォーマット", - "xpack.ml.fileDatavisualizer.analysisSummary.grokPatternTitle": "Grok パターン", - "xpack.ml.fileDatavisualizer.analysisSummary.hasHeaderRowTitle": "ヘッダー行があります", - "xpack.ml.fileDatavisualizer.analysisSummary.summaryTitle": "まとめ", - "xpack.ml.fileDatavisualizer.analysisSummary.timeFieldTitle": "時間フィールド", - "xpack.ml.fileDatavisualizer.bottomBar.backButtonLabel": "戻る", - "xpack.ml.fileDatavisualizer.bottomBar.cancelButtonLabel": "キャンセル", - "xpack.ml.fileDatavisualizer.bottomBar.missingImportPrivilegesMessage": "データインポートを有効にするには、ingest_adminロールが必要です", - "xpack.ml.fileDatavisualizer.bottomBar.readMode.cancelButtonLabel": "キャンセル", - "xpack.ml.fileDatavisualizer.bottomBar.readMode.importButtonLabel": "インポート", - "xpack.ml.fileDatavisualizer.combinedFieldsForm.mappingsParseError": "マッピングのパース中にエラーが発生しました:{error}", - "xpack.ml.fileDatavisualizer.combinedFieldsForm.pipelineParseError": "パイプラインのパース中にエラーが発生しました:{error}", - "xpack.ml.fileDatavisualizer.combinedFieldsLabel": "結合されたフィールド", - "xpack.ml.fileDatavisualizer.combinedFieldsReadOnlyHelpTextLabel": "詳細タグで結合されたフィールドを編集", - "xpack.ml.fileDatavisualizer.combinedFieldsReadOnlyLabel": "結合されたフィールド", - "xpack.ml.fileDatavisualizer.editFlyout.applyOverrideSettingsButtonLabel": "適用", - "xpack.ml.fileDatavisualizer.editFlyout.closeOverrideSettingsButtonLabel": "閉じる", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.customDelimiterFormRowLabel": "カスタム区切り記号", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.customTimestampFormatErrorMessage": "タイムスタンプのフォーマットは、これらの Java 日付/時刻フォーマットの組み合わせでなければなりません:\n yy, yyyy, M, MM, MMM, MMMM, d, dd, EEE, EEEE, H, HH, h, mm, ss, S-SSSSSSSSS, a, XX, XXX, zzz", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.customTimestampFormatFormRowLabel": "カスタムタイムスタンプフォーマット", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.dataFormatFormRowLabel": "データフォーマット", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.delimiterFormRowLabel": "区切り記号", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.editFieldNamesTitle": "フィールド名の編集", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.grokPatternFormRowLabel": "Grok パターン", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.hasHeaderRowLabel": "ヘッダー行があります", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.linesToSampleErrorMessage": "値は {min} よりも大きく {max} 以下でなければなりません", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.linesToSampleFormRowLabel": "サンプルする行数", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.quoteCharacterFormRowLabel": "引用符", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timeFieldFormRowLabel": "時間フィールド", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampEmptyValidationErrorMessage": "タイムスタンプフォーマットにタイムフォーマット文字グループがありません {timestampFormat}", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampFormatFormRowLabel": "タイムスタンプフォーマット", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampFormatHelpText": "対応フォーマットの詳細をご覧ください", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampLetterSValidationErrorMessage": "{format} の文字 { length, plural, one { {lg} } other { グループ {lg} } } は、ss と {sep} からの区切りで始まっていないため、サポートされていません", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampLetterValidationErrorMessage": "{format} の文字 { length, plural, one { {lg} } other { グループ {lg} } } はサポートされていません", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampQuestionMarkValidationErrorMessage": "タイムスタンプフォーマット {timestampFormat} は、疑問符 ({fieldPlaceholder}) が含まれているためサポートされていません", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.trimFieldsLabel": "フィールドを切り抜く", - "xpack.ml.fileDatavisualizer.editFlyout.overrideSettingsTitle": "上書き設定", - "xpack.ml.fileDatavisualizer.experimentalBadge.experimentalLabel": "実験的", - "xpack.ml.fileDatavisualizer.explanationFlyout.closeButton": "閉じる", - "xpack.ml.fileDatavisualizer.explanationFlyout.content": "分析結果を生成した論理ステップ。", - "xpack.ml.fileDatavisualizer.explanationFlyout.title": "分析説明", - "xpack.ml.fileDatavisualizer.fieldStatsCard.maxTitle": "最高", - "xpack.ml.fileDatavisualizer.fieldStatsCard.medianTitle": "中間", - "xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle": "分", - "xpack.ml.fileDatavisualizer.fileBeatConfig.paths": "ファイルのパスをここに追加してください", - "xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.closeButton": "閉じる", - "xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.copyButton": "クリップボードにコピー", - "xpack.ml.fileDatavisualizer.fileContents.fileContentsTitle": "ファイルコンテンツ", - "xpack.ml.fileDatavisualizer.fileDatavisualizerView.xmlNotCurrentlySupportedErrorMessage": "XML は現在サポートされていません", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.applyOverridesDescription": "ファイル形式やタイムスタンプ形式などこのデータに関する何らかの情報がある場合は、初期オーバーライドを追加すると、残りの構造を推論するのに役立つことがあります。", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.fileCouldNotBeReadTitle": "ファイル構造を決定できません", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.fileSizeExceedsAllowedSizeByDiffFormatErrorMessage": "アップロードするよう選択されたファイルのサイズが {diffFormatted} に許可された最大サイズの {maxFileSizeFormatted} を超えています", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.fileSizeExceedsAllowedSizeErrorMessage": "アップロードするよう選択されたファイルのサイズは {fileSizeFormatted} で、許可された最大サイズの {maxFileSizeFormatted} を超えています。", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.fileSizeTooLargeTitle": "ファイルサイズが大きすぎます。", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.overrideButton": "上書き設定を適用", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.revertingToPreviousSettingsDescription": "以前の設定に戻しています。", - "xpack.ml.fileDatavisualizer.geoPointCombinedFieldLabel": "地理ポイントフィールドを追加", - "xpack.ml.fileDatavisualizer.geoPointForm.geoPointFieldAriaLabel": "地理ポイントフィールド、必須フィールド", - "xpack.ml.fileDatavisualizer.geoPointForm.geoPointFieldLabel": "地理ポイントフィールド", - "xpack.ml.fileDatavisualizer.geoPointForm.latFieldLabel": "緯度フィールド", - "xpack.ml.fileDatavisualizer.geoPointForm.lonFieldLabel": "経度フィールド", - "xpack.ml.fileDatavisualizer.geoPointForm.submitButtonLabel": "追加", - "xpack.ml.fileDatavisualizer.importErrors.checkingPermissionErrorMessage": "パーミッションエラーをインポートします", - "xpack.ml.fileDatavisualizer.importErrors.creatingIndexErrorMessage": "インデックスの作成中にエラーが発生しました", - "xpack.ml.fileDatavisualizer.importErrors.creatingIndexPatternErrorMessage": "インデックスパターンの作成中にエラーが発生しました", - "xpack.ml.fileDatavisualizer.importErrors.creatingIngestPipelineErrorMessage": "投入パイプラインの作成中にエラーが発生しました", - "xpack.ml.fileDatavisualizer.importErrors.defaultErrorMessage": "エラー", - "xpack.ml.fileDatavisualizer.importErrors.moreButtonLabel": "詳細", - "xpack.ml.fileDatavisualizer.importErrors.parsingJSONErrorMessage": "JSON のパース中にエラーが発生しました", - "xpack.ml.fileDatavisualizer.importErrors.readingFileErrorMessage": "ファイルの読み込み中にエラーが発生しました", - "xpack.ml.fileDatavisualizer.importErrors.unknownErrorMessage": "不明なエラー", - "xpack.ml.fileDatavisualizer.importErrors.uploadingDataErrorMessage": "データのアップロード中にエラーが発生しました", - "xpack.ml.fileDatavisualizer.importProgress.createIndexPatternTitle": "インデックスパターンを作成", - "xpack.ml.fileDatavisualizer.importProgress.createIndexTitle": "インデックスの作成", - "xpack.ml.fileDatavisualizer.importProgress.createIngestPipelineTitle": "投入パイプラインの作成", - "xpack.ml.fileDatavisualizer.importProgress.creatingIndexPatternDescription": "インデックスパターンを作成中です", - "xpack.ml.fileDatavisualizer.importProgress.creatingIndexPatternTitle": "インデックスパターンを作成中です", - "xpack.ml.fileDatavisualizer.importProgress.creatingIndexTitle": "インデックスを作成中です", - "xpack.ml.fileDatavisualizer.importProgress.creatingIngestPipelineTitle": "投入パイプラインを作成中", - "xpack.ml.fileDatavisualizer.importProgress.dataUploadedTitle": "データがアップロードされました", - "xpack.ml.fileDatavisualizer.importProgress.fileProcessedTitle": "ファイルが処理されました", - "xpack.ml.fileDatavisualizer.importProgress.indexCreatedTitle": "インデックスが作成されました", - "xpack.ml.fileDatavisualizer.importProgress.indexPatternCreatedTitle": "インデックスパターンが作成されました", - "xpack.ml.fileDatavisualizer.importProgress.ingestPipelineCreatedTitle": "投入パイプラインが作成されました", - "xpack.ml.fileDatavisualizer.importProgress.processFileTitle": "ファイルの処理", - "xpack.ml.fileDatavisualizer.importProgress.processingFileTitle": "ファイルを処理中", - "xpack.ml.fileDatavisualizer.importProgress.processingImportedFileDescription": "インポートするファイルを処理中", - "xpack.ml.fileDatavisualizer.importProgress.stepTwoCreatingIndexDescription": "インデックスを作成中です", - "xpack.ml.fileDatavisualizer.importProgress.stepTwoCreatingIndexIngestPipelineDescription": "インデックスと投入パイプラインを作成中です", - "xpack.ml.fileDatavisualizer.importProgress.uploadDataTitle": "データのアップロード", - "xpack.ml.fileDatavisualizer.importProgress.uploadingDataDescription": "データをアップロード中です", - "xpack.ml.fileDatavisualizer.importProgress.uploadingDataTitle": "データをアップロード中です", - "xpack.ml.fileDatavisualizer.importSettings.advancedTabName": "高度な設定", - "xpack.ml.fileDatavisualizer.importSettings.simpleTabName": "シンプル", - "xpack.ml.fileDatavisualizer.importSummary.documentsCouldNotBeImportedDescription": "{importFailuresLength}/{docCount} 個のドキュメントをインポートできませんでした。行が Grok パターンと一致していないことが原因の可能性があります。", - "xpack.ml.fileDatavisualizer.importSummary.documentsCouldNotBeImportedTitle": "ドキュメントの一部をインポートできませんでした。", - "xpack.ml.fileDatavisualizer.importSummary.documentsIngestedTitle": "ドキュメントが投入されました", - "xpack.ml.fileDatavisualizer.importSummary.failedDocumentsButtonLabel": "失敗したドキュメント", - "xpack.ml.fileDatavisualizer.importSummary.failedDocumentsTitle": "失敗したドキュメント", - "xpack.ml.fileDatavisualizer.importSummary.importCompleteTitle": "インポート完了", - "xpack.ml.fileDatavisualizer.importSummary.indexPatternTitle": "インデックスパターン", - "xpack.ml.fileDatavisualizer.importSummary.indexTitle": "インデックス", - "xpack.ml.fileDatavisualizer.importSummary.ingestPipelineTitle": "パイプラインを投入", - "xpack.ml.fileDatavisualizer.importView.experimentalFeatureTooltip": "実験的機能。フィードバックをお待ちしています。", - "xpack.ml.fileDatavisualizer.importView.importButtonLabel": "インポート", - "xpack.ml.fileDatavisualizer.importView.importDataTitle": "データのインポート", - "xpack.ml.fileDatavisualizer.importView.importPermissionError": "インデックス {index} にデータを作成またはインポートするパーミッションがありません。", - "xpack.ml.fileDatavisualizer.importView.indexNameAlreadyExistsErrorMessage": "インデックス名がすでに存在します", - "xpack.ml.fileDatavisualizer.importView.indexNameContainsIllegalCharactersErrorMessage": "インデックス名に許可されていない文字が含まれています。", - "xpack.ml.fileDatavisualizer.importView.indexPatternDoesNotMatchIndexNameErrorMessage": "インデックスパターンがインデックス名と一致しません", - "xpack.ml.fileDatavisualizer.importView.indexPatternNameAlreadyExistsErrorMessage": "インデックスパターン名がすでに存在します", - "xpack.ml.fileDatavisualizer.importView.parseMappingsError": "マッピングのパース中にエラーが発生しました:", - "xpack.ml.fileDatavisualizer.importView.parsePipelineError": "投入パイプラインのパース中にエラーが発生しました:", - "xpack.ml.fileDatavisualizer.importView.parseSettingsError": "設定のパース中にエラーが発生しました:", - "xpack.ml.fileDatavisualizer.importView.resetButtonLabel": "リセット", - "xpack.ml.fileDatavisualizer.nameCollisionMsg": "「{name}」はすでに存在します。一意の名前を入力してください。", - "xpack.ml.fileDatavisualizer.removeCombinedFieldsLabel": "結合されたフィールドを削除", - "xpack.ml.fileDatavisualizer.resultsLinks.createNewMLJobTitle": "新規 ML ジョブの作成", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfig": "Filebeat 構成を作成", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigBottomText": "{password} が {user} ユーザーのパスワードである場合、{esUrl} は Elasticsearch の URL です。", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigBottomTextNoUsername": "{esUrl} が Elasticsearch の URL である場合", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTitle": "Filebeat 構成", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTopText1": "Filebeat を使用して {index} インデックスに追加データをアップロードできます。", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTopText2": "{filebeatYml} を修正して接続情報を設定します。", - "xpack.ml.fileDatavisualizer.resultsLinks.indexManagementTitle": "インデックス管理", - "xpack.ml.fileDatavisualizer.resultsLinks.indexPatternManagementTitle": "インデックスパターン管理", - "xpack.ml.fileDatavisualizer.resultsLinks.openInDataVisualizerTitle": "データビジュアライザーを開く", - "xpack.ml.fileDatavisualizer.resultsLinks.viewIndexInDiscoverTitle": "インデックスを Discover で表示", - "xpack.ml.fileDatavisualizer.resultsView.analysisExplanationButtonLabel": "分析説明", - "xpack.ml.fileDatavisualizer.resultsView.fileStatsName": "ファイル統計", - "xpack.ml.fileDatavisualizer.resultsView.overrideSettingsButtonLabel": "上書き設定", - "xpack.ml.fileDatavisualizer.simpleImportSettings.createIndexPatternLabel": "インデックスパターンを作成", - "xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameAriaLabel": "インデックス名、必須フィールド", - "xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameFormRowLabel": "インデックス名", - "xpack.ml.fileDatavisualizer.simpleImportSettings.indexNamePlaceholder": "インデックス名", - "xpack.ml.fileDatavisualizer.welcomeContent.delimitedTextFilesDescription": "CSV や TSV などの区切られたテキストファイル", - "xpack.ml.fileDatavisualizer.welcomeContent.experimentalFeatureDescription": "これは実験的な機能です。フィードバックがありますか?{githubLink}で問題を報告してください。", - "xpack.ml.fileDatavisualizer.welcomeContent.experimentalFeatureTooltip": "実験的機能。フィードバックをお待ちしています。", - "xpack.ml.fileDatavisualizer.welcomeContent.logFilesWithCommonFormatDescription": "タイムスタンプの一般的フォーマットのログファイル", - "xpack.ml.fileDatavisualizer.welcomeContent.newlineDelimitedJsonDescription": "改行区切りの JSON", - "xpack.ml.fileDatavisualizer.welcomeContent.supportedFileFormatDescription": "ファイルデータビジュアライザーはこれらのファイル形式をサポートしています:", - "xpack.ml.fileDatavisualizer.welcomeContent.uploadedFilesAllowedSizeDescription": "最大{maxFileSize}のファイルをアップロードできます。", - "xpack.ml.fileDatavisualizer.welcomeContent.visualizeDataFromLogFileDescription": "ファイルデータビジュアライザーは、ログファイルのフィールドとメトリックの理解に役立ちます。ファイルをアップロードして、データを分析し、 Elasticsearch インデックスにインポートするか選択できます。", - "xpack.ml.fileDatavisualizer.welcomeContent.visualizeDataFromLogFileTitle": "ログファイルのデータを可視化 {experimentalBadge}", "xpack.ml.fileDataVisualizerDescription": "CSV、NDJSON、またはログファイルをインポートします。", "xpack.ml.fileDataVisualizerTitle": "ファイルをアップロード", "xpack.ml.formatters.metricChangeDescription.actualSameAsTypicalDescription": "実際値が通常値と同じ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a9135a02869ad0a..d5f8c7533a54cc0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13776,8 +13776,6 @@ "xpack.ml.datavisualizer.dataGrid.showDistributionsAriaLabel": "显示分布", "xpack.ml.datavisualizer.dataGrid.typeColumnName": "类型", "xpack.ml.datavisualizer.dataLoader.internalServerErrorMessage": "加载索引 {index} 中的数据时出错。{message}。请求可能已超时。请尝试使用较小的样例大小或缩小时间范围。", - "xpack.ml.dataVisualizer.fileBased.fieldNameSelect": "字段名称", - "xpack.ml.dataVisualizer.fileBased.fieldTypeSelect": "字段类型", "xpack.ml.dataVisualizer.fileBasedLabel": "文件", "xpack.ml.dataVisualizer.indexBased.fieldNameSelect": "字段名称", "xpack.ml.dataVisualizer.indexBased.fieldTypeSelect": "字段类型", @@ -13960,165 +13958,6 @@ "xpack.ml.fieldTypeIcon.numberTypeAriaLabel": "数字类型", "xpack.ml.fieldTypeIcon.textTypeAriaLabel": "文本类型", "xpack.ml.fieldTypeIcon.unknownTypeAriaLabel": "未知类型", - "xpack.ml.fileDatavisualizer.aboutPanel.analyzingDataTitle": "正在分析数据", - "xpack.ml.fileDatavisualizer.aboutPanel.selectOrDragAndDropFileDescription": "选择或拖放文件", - "xpack.ml.fileDatavisualizer.addCombinedFieldsLabel": "添加组合字段", - "xpack.ml.fileDatavisualizer.advancedImportSettings.createIndexPatternLabel": "创建索引模式", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexNameAriaLabel": "索引名称,必填字段", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexNameLabel": "索引名称", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexNamePlaceholder": "索引名称", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexPatternNameLabel": "索引模式名称", - "xpack.ml.fileDatavisualizer.advancedImportSettings.indexSettingsLabel": "索引设置", - "xpack.ml.fileDatavisualizer.advancedImportSettings.ingestPipelineLabel": "采集管道", - "xpack.ml.fileDatavisualizer.advancedImportSettings.mappingsLabel": "映射", - "xpack.ml.fileDatavisualizer.analysisSummary.analyzedLinesNumberTitle": "已分析的行数", - "xpack.ml.fileDatavisualizer.analysisSummary.delimiterTitle": "分隔符", - "xpack.ml.fileDatavisualizer.analysisSummary.formatTitle": "格式", - "xpack.ml.fileDatavisualizer.analysisSummary.grokPatternTitle": "Grok 模式", - "xpack.ml.fileDatavisualizer.analysisSummary.hasHeaderRowTitle": "包含标题行", - "xpack.ml.fileDatavisualizer.analysisSummary.summaryTitle": "摘要", - "xpack.ml.fileDatavisualizer.analysisSummary.timeFieldTitle": "时间字段", - "xpack.ml.fileDatavisualizer.analysisSummary.timeFormatTitle": "时间{timestampFormats, plural, other {格式}}", - "xpack.ml.fileDatavisualizer.bottomBar.backButtonLabel": "返回", - "xpack.ml.fileDatavisualizer.bottomBar.cancelButtonLabel": "取消", - "xpack.ml.fileDatavisualizer.bottomBar.missingImportPrivilegesMessage": "您需要具有 ingest_admin 角色才能启用数据导入", - "xpack.ml.fileDatavisualizer.bottomBar.readMode.cancelButtonLabel": "取消", - "xpack.ml.fileDatavisualizer.bottomBar.readMode.importButtonLabel": "导入", - "xpack.ml.fileDatavisualizer.combinedFieldsForm.mappingsParseError": "解析映射时出错:{error}", - "xpack.ml.fileDatavisualizer.combinedFieldsForm.pipelineParseError": "解析管道时出错:{error}", - "xpack.ml.fileDatavisualizer.combinedFieldsLabel": "组合字段", - "xpack.ml.fileDatavisualizer.combinedFieldsReadOnlyHelpTextLabel": "在高级选项卡中编辑组合字段", - "xpack.ml.fileDatavisualizer.combinedFieldsReadOnlyLabel": "组合字段", - "xpack.ml.fileDatavisualizer.editFlyout.applyOverrideSettingsButtonLabel": "应用", - "xpack.ml.fileDatavisualizer.editFlyout.closeOverrideSettingsButtonLabel": "关闭", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.customDelimiterFormRowLabel": "定制分隔符", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.customTimestampFormatErrorMessage": "时间戳格式必须为以下 Java 日期/时间格式的组合:\n yy、yyyy、M、MM、MMM、MMMM、d、dd、EEE、EEEE、H、HH、h、mm、ss、S 至 SSSSSSSSS、a、XX、XXX、zzz", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.customTimestampFormatFormRowLabel": "定制时间戳格式", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.dataFormatFormRowLabel": "数据格式", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.delimiterFormRowLabel": "分隔符", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.editFieldNamesTitle": "编辑字段名称", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.grokPatternFormRowLabel": "Grok 模式", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.hasHeaderRowLabel": "包含标题行", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.linesToSampleErrorMessage": "值必须大于 {min} 并小于或等于 {max}", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.linesToSampleFormRowLabel": "要采样的行数", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.quoteCharacterFormRowLabel": "引用字符", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timeFieldFormRowLabel": "时间字段", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampEmptyValidationErrorMessage": "时间戳格式 {timestampFormat} 中没有时间格式字母组", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampFormatFormRowLabel": "时间戳格式", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampFormatHelpText": "请参阅有关接受格式的更多内容。", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampLetterSValidationErrorMessage": "{format}的字母 { length, plural, one { {lg} } other { 组 {lg} } } 不受支持,因为其未前置 ss 和 {sep} 中的分隔符", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampLetterValidationErrorMessage": "{format}的字母 { length, plural, one { {lg} } other { 组 {lg} } } 不受支持", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampQuestionMarkValidationErrorMessage": "时间戳格式 {timestampFormat} 不受支持,因为其包含问号字符 ({fieldPlaceholder})", - "xpack.ml.fileDatavisualizer.editFlyout.overrides.trimFieldsLabel": "应剪裁字段", - "xpack.ml.fileDatavisualizer.editFlyout.overrideSettingsTitle": "替代设置", - "xpack.ml.fileDatavisualizer.experimentalBadge.experimentalLabel": "实验性", - "xpack.ml.fileDatavisualizer.explanationFlyout.closeButton": "关闭", - "xpack.ml.fileDatavisualizer.explanationFlyout.content": "产生分析结果的逻辑步骤。", - "xpack.ml.fileDatavisualizer.explanationFlyout.title": "分析说明", - "xpack.ml.fileDatavisualizer.fieldStatsCard.maxTitle": "最大值", - "xpack.ml.fileDatavisualizer.fieldStatsCard.medianTitle": "中值", - "xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle": "最小值", - "xpack.ml.fileDatavisualizer.fileBeatConfig.paths": "在此处将路径添加您的文件中", - "xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.closeButton": "关闭", - "xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.copyButton": "复制到剪贴板", - "xpack.ml.fileDatavisualizer.fileContents.fileContentsTitle": "文件内容", - "xpack.ml.fileDatavisualizer.fileContents.firstLinesDescription": "前 {numberOfLines, plural, other {# 行}}", - "xpack.ml.fileDatavisualizer.fileDatavisualizerView.xmlNotCurrentlySupportedErrorMessage": "当前不支持 XML", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.applyOverridesDescription": "如果您对此数据有所了解,例如文件格式或时间戳格式,则添加初始覆盖可以帮助我们推理结构的其余部分。", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.fileCouldNotBeReadTitle": "无法确定文件结构", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.fileSizeExceedsAllowedSizeByDiffFormatErrorMessage": "您选择用于上传的文件大小超过上限值 {maxFileSizeFormatted} 的 {diffFormatted}", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.fileSizeExceedsAllowedSizeErrorMessage": "您选择用于上传的文件大小为 {fileSizeFormatted},超过上限值 {maxFileSizeFormatted}", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.fileSizeTooLargeTitle": "文件太大", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.overrideButton": "应用覆盖设置", - "xpack.ml.fileDatavisualizer.fileErrorCallouts.revertingToPreviousSettingsDescription": "恢复到以前的设置", - "xpack.ml.fileDatavisualizer.geoPointCombinedFieldLabel": "添加地理点字段", - "xpack.ml.fileDatavisualizer.geoPointForm.geoPointFieldAriaLabel": "地理点字段,必填字段", - "xpack.ml.fileDatavisualizer.geoPointForm.geoPointFieldLabel": "地理点字段", - "xpack.ml.fileDatavisualizer.geoPointForm.latFieldLabel": "纬度字段", - "xpack.ml.fileDatavisualizer.geoPointForm.lonFieldLabel": "经度字段", - "xpack.ml.fileDatavisualizer.geoPointForm.submitButtonLabel": "添加", - "xpack.ml.fileDatavisualizer.importErrors.checkingPermissionErrorMessage": "导入权限错误", - "xpack.ml.fileDatavisualizer.importErrors.creatingIndexErrorMessage": "创建索引时出错", - "xpack.ml.fileDatavisualizer.importErrors.creatingIndexPatternErrorMessage": "创建索引模式时出错", - "xpack.ml.fileDatavisualizer.importErrors.creatingIngestPipelineErrorMessage": "创建采集管道时出错", - "xpack.ml.fileDatavisualizer.importErrors.defaultErrorMessage": "错误", - "xpack.ml.fileDatavisualizer.importErrors.moreButtonLabel": "更多", - "xpack.ml.fileDatavisualizer.importErrors.parsingJSONErrorMessage": "解析 JSON 出错", - "xpack.ml.fileDatavisualizer.importErrors.readingFileErrorMessage": "读取文件时出错", - "xpack.ml.fileDatavisualizer.importErrors.unknownErrorMessage": "未知错误", - "xpack.ml.fileDatavisualizer.importErrors.uploadingDataErrorMessage": "上传数据时出错", - "xpack.ml.fileDatavisualizer.importProgress.createIndexPatternTitle": "创建索引模式", - "xpack.ml.fileDatavisualizer.importProgress.createIndexTitle": "创建索引", - "xpack.ml.fileDatavisualizer.importProgress.createIngestPipelineTitle": "创建采集管道", - "xpack.ml.fileDatavisualizer.importProgress.creatingIndexPatternDescription": "正在创建索引模式", - "xpack.ml.fileDatavisualizer.importProgress.creatingIndexPatternTitle": "正在创建索引模式", - "xpack.ml.fileDatavisualizer.importProgress.creatingIndexTitle": "正在创建索引", - "xpack.ml.fileDatavisualizer.importProgress.creatingIngestPipelineTitle": "正在创建采集管道", - "xpack.ml.fileDatavisualizer.importProgress.dataUploadedTitle": "数据已上传", - "xpack.ml.fileDatavisualizer.importProgress.fileProcessedTitle": "文件已处理", - "xpack.ml.fileDatavisualizer.importProgress.indexCreatedTitle": "索引已创建", - "xpack.ml.fileDatavisualizer.importProgress.indexPatternCreatedTitle": "索引模式已创建", - "xpack.ml.fileDatavisualizer.importProgress.ingestPipelineCreatedTitle": "采集管道已创建", - "xpack.ml.fileDatavisualizer.importProgress.processFileTitle": "处理文件", - "xpack.ml.fileDatavisualizer.importProgress.processingFileTitle": "正在处理文件", - "xpack.ml.fileDatavisualizer.importProgress.processingImportedFileDescription": "正在处理要导入的文件", - "xpack.ml.fileDatavisualizer.importProgress.stepTwoCreatingIndexDescription": "正在创建索引", - "xpack.ml.fileDatavisualizer.importProgress.stepTwoCreatingIndexIngestPipelineDescription": "正在创建索引和采集管道", - "xpack.ml.fileDatavisualizer.importProgress.uploadDataTitle": "上传数据", - "xpack.ml.fileDatavisualizer.importProgress.uploadingDataDescription": "正在上传数据", - "xpack.ml.fileDatavisualizer.importProgress.uploadingDataTitle": "正在上传数据", - "xpack.ml.fileDatavisualizer.importSettings.advancedTabName": "高级", - "xpack.ml.fileDatavisualizer.importSettings.simpleTabName": "简单", - "xpack.ml.fileDatavisualizer.importSummary.documentsCouldNotBeImportedDescription": "无法导入 {importFailuresLength} 个文档,共 {docCount} 个。这可能是由于行与 Grok 模式不匹配。", - "xpack.ml.fileDatavisualizer.importSummary.documentsCouldNotBeImportedTitle": "部分文档无法导入", - "xpack.ml.fileDatavisualizer.importSummary.documentsIngestedTitle": "已采集的文档", - "xpack.ml.fileDatavisualizer.importSummary.failedDocumentsButtonLabel": "失败的文档", - "xpack.ml.fileDatavisualizer.importSummary.failedDocumentsTitle": "失败的文档", - "xpack.ml.fileDatavisualizer.importSummary.importCompleteTitle": "导入完成", - "xpack.ml.fileDatavisualizer.importSummary.indexPatternTitle": "索引模式", - "xpack.ml.fileDatavisualizer.importSummary.indexTitle": "索引", - "xpack.ml.fileDatavisualizer.importSummary.ingestPipelineTitle": "采集管道", - "xpack.ml.fileDatavisualizer.importView.experimentalFeatureTooltip": "实验性功能。我们很乐意听取您的反馈意见。", - "xpack.ml.fileDatavisualizer.importView.importButtonLabel": "导入", - "xpack.ml.fileDatavisualizer.importView.importDataTitle": "导入数据", - "xpack.ml.fileDatavisualizer.importView.importPermissionError": "您无权创建或将数据导入索引 {index}", - "xpack.ml.fileDatavisualizer.importView.indexNameAlreadyExistsErrorMessage": "索引名称已存在", - "xpack.ml.fileDatavisualizer.importView.indexNameContainsIllegalCharactersErrorMessage": "索引名称包含非法字符", - "xpack.ml.fileDatavisualizer.importView.indexPatternDoesNotMatchIndexNameErrorMessage": "索引模式与索引名称不匹配", - "xpack.ml.fileDatavisualizer.importView.indexPatternNameAlreadyExistsErrorMessage": "索引模式名称已存在", - "xpack.ml.fileDatavisualizer.importView.parseMappingsError": "解析映射时出错:", - "xpack.ml.fileDatavisualizer.importView.parsePipelineError": "解析采集管道时出错:", - "xpack.ml.fileDatavisualizer.importView.parseSettingsError": "解析设置时出错:", - "xpack.ml.fileDatavisualizer.importView.resetButtonLabel": "重置", - "xpack.ml.fileDatavisualizer.nameCollisionMsg": "“{name}”已存在,请提供唯一名称", - "xpack.ml.fileDatavisualizer.removeCombinedFieldsLabel": "移除组合字段", - "xpack.ml.fileDatavisualizer.resultsLinks.createNewMLJobTitle": "新建 ML 作业", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfig": "创建 Filebeat 配置", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigBottomText": "其中 {password} 是 {user} 用户的密码,{esUrl} 是 Elasticsearch 的 URL。", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigBottomTextNoUsername": "其中 {esUrl} 是 Elasticsearch 的 URL。", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTitle": "Filebeat 配置", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTopText1": "可以使用 Filebeat 将其他数据上传到 {index} 索引。", - "xpack.ml.fileDatavisualizer.resultsLinks.fileBeatConfigTopText2": "修改 {filebeatYml} 以设置连接信息:", - "xpack.ml.fileDatavisualizer.resultsLinks.indexManagementTitle": "索引管理", - "xpack.ml.fileDatavisualizer.resultsLinks.indexPatternManagementTitle": "索引模式管理", - "xpack.ml.fileDatavisualizer.resultsLinks.openInDataVisualizerTitle": "在数据可视化工具中打开", - "xpack.ml.fileDatavisualizer.resultsLinks.viewIndexInDiscoverTitle": "在 Discover 中查看索引", - "xpack.ml.fileDatavisualizer.resultsView.analysisExplanationButtonLabel": "分析说明", - "xpack.ml.fileDatavisualizer.resultsView.fileStatsName": "文件统计", - "xpack.ml.fileDatavisualizer.resultsView.overrideSettingsButtonLabel": "替代设置", - "xpack.ml.fileDatavisualizer.simpleImportSettings.createIndexPatternLabel": "创建索引模式", - "xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameAriaLabel": "索引名称,必填字段", - "xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameFormRowLabel": "索引名称", - "xpack.ml.fileDatavisualizer.simpleImportSettings.indexNamePlaceholder": "索引名称", - "xpack.ml.fileDatavisualizer.welcomeContent.delimitedTextFilesDescription": "分隔的文本文件,例如 CSV 和 TSV", - "xpack.ml.fileDatavisualizer.welcomeContent.experimentalFeatureDescription": "此功能为实验性功能。有反馈?如欲提供反馈,请在 {githubLink} 中创建问题。", - "xpack.ml.fileDatavisualizer.welcomeContent.experimentalFeatureTooltip": "实验性功能。我们很乐意听取您的反馈意见。", - "xpack.ml.fileDatavisualizer.welcomeContent.logFilesWithCommonFormatDescription": "具有时间戳通用格式的日志文件", - "xpack.ml.fileDatavisualizer.welcomeContent.newlineDelimitedJsonDescription": "换行符分隔的 JSON", - "xpack.ml.fileDatavisualizer.welcomeContent.supportedFileFormatDescription": "File Data Visualizer 支持以下文件格式:", - "xpack.ml.fileDatavisualizer.welcomeContent.uploadedFilesAllowedSizeDescription": "您可以上传不超过 {maxFileSize} 的文件。", - "xpack.ml.fileDatavisualizer.welcomeContent.visualizeDataFromLogFileDescription": "File Data Visualizer 可帮助您理解日志文件中的字段和指标。上传文件、分析文件数据,然后选择是否将数据导入 Elasticsearch 索引。", - "xpack.ml.fileDatavisualizer.welcomeContent.visualizeDataFromLogFileTitle": "可视化来自日志文件的数据 {experimentalBadge}", "xpack.ml.fileDataVisualizerDescription": "导入您自己的 CSV、NDJSON 或日志文件。", "xpack.ml.fileDataVisualizerTitle": "上传文件", "xpack.ml.formatters.metricChangeDescription.actualSameAsTypicalDescription": "实际上与典型模式相同",